Project import generated by Copybara.

GitOrigin-RevId: fa51c7997b87c02a156a981c24b23aff3a045c6b
diff --git a/licenses/binaries/COPYING b/COPYING
similarity index 100%
rename from licenses/binaries/COPYING
rename to COPYING
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..dd58138
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,6387 @@
+Summary of changes from v181 to v182
+============================================
+
+Kay Sievers (22):
+      build-sys: unpack test sysfs only for 'make check'
+      build-sys: add --disable-manpages
+      update sd-daemon files
+      test: remove outdated key attributes
+      update TOO
+      builtin: path_id - remove dead cciss code
+      rules: do not create by-id/scsi-* links for ATA devices
+      remove udev-acl
+      udev.conf - do not set any value by default
+      move src/extras subdirectories to src/
+      rules: delete outdated 30-kernel-compat.rules
+      rules: move 42-qemu-usb.rules to rules/ dir
+      remove edd_id extra
+      build-sys: remove empty directory
+      rules: delete s390 rules, they will move to s390utils
+      update TODO
+      rules: move all rules to top level rules/ dir
+      extras: path_id - skip ATA transport class devices
+      extras: path_id - add comment about readdir() rebase logic
+      extras: ata_id - do not log error if HDIO_GET_IDENTITY fails
+      rules sort order: /lib, /run, /etc
+      build-sys: place build binaries in the root
+
+Matthew Garrett (1):
+      rules: Enable USB autosuspend on more USB HID devices
+
+
+Summary of changes from v180 to v181
+============================================
+
+Andreas Schwab (1):
+      ata_id: fix identify string fixup
+
+Bruno Redondi (1):
+      keymap: Add Fujitsu Siemens Amilo Li 2732
+
+James M. Leddy (1):
+      keymap: Fix touchpad toggle button on Lenovo Ideapad
+
+Kay Sievers (4):
+      configure: show ROOTPREFIX in firmware path option help text
+      extras: cdrom_id -  create /dev/cdrom and conditionally /dev/dvd for sr0
+      extras: cdrom_id -  create only /dev/cdrom
+      ata_id: whitespace fixes
+
+Lucas De Marchi (1):
+      builtin: kmod - depend on libkmod >= 5
+
+
+Summary of changes from v179 to v180
+============================================
+
+Kay Sievers (4):
+      Makefile: update kernel.org hooks
+      build-sys: we need to install shipped man pages without xsltproc installed
+      builtin: blkid - add missing ID_ prefix for PART_ENTRY_* keys
+      do not stop rule processing when device node is no longer around
+
+
+Summary of changes from v178 to v179
+============================================
+
+Kay Sievers (8):
+      fix some fallout from tab removal
+      use devnode() for $name not sysname(), device nodes might be in a subdirectory
+      print warning when rules try to rename kernel device nodes
+      move variable inside condition
+      update TODO
+      build-sys: enable everything for 'make distcheck'
+      use sysname() for devices without a device node
+      fix path to extras
+
+
+Summary of changes from v177 to v178
+============================================
+
+Evan Nemerson (1):
+      gudev: several minor introspection fixes
+
+Kay Sievers (7):
+      Makefile: update kernel.org doc hooks for kup
+      builtin: blkid - add missing ID_ prefix
+      udevd: kill hanging event processes after 30 seconds
+      Makefile: switch from .asc to .sign
+      rules: rtc - point /dev/rtc symlink to 'hctosys' device
+      warn about deprecated RUN+="socket:" use
+      libudev: do not set DEVNAME= twice
+
+Martin Pitt (4):
+      keymap: Fix rfkill button on Hewlett-Packard HP ProBook
+      keymap: Fix eject button on Samsung 700Z series
+      keymap: Fix keyboard brightness keys on Samsung 700Z series
+      keymap: Add Alienware M14xR1
+
+
+Summary of changes from v176 to v177
+============================================
+
+Kay Sievers (3):
+      Makefile: update kernel.org sign and upload hook
+      rule_generator: fix to install rules in rules.d/
+      rule_generator: use += for dist_udevhome_DATA
+
+
+Summary of changes from v175 to v176
+============================================
+
+Alan Stern (1):
+      [PATCH[ udev: ata_id: Fix length of INQUIRY command
+
+Kay Sievers (61):
+      libudev: print log_fn address instead of ctx when setting logging function
+      do not ship autogen.sh in the tarball
+      man: clarify 'config file stack'
+      rename 'init' directory to 'systemd'
+      systemd: use PassCred=yes
+      use libexecdir, bindir, sbindir, switch to /usr/lib/udev in documentation
+      configure: fix typo
+      make: do not (mis-)use the config file generator, create .xz tarball
+      prepare builtins for blkid and kmod
+      add builtin load/unload initializers
+      build argv[] for builtin commands
+      update blkid builtin
+      rules: switch to built-in blkid
+      rules: do not preprocess 60-persistent-storage.rules
+      buildsys: disable tar.gz
+      builtin: blkid - add missing newline
+      builtin: blkid - add missing ID_FS_USAGE
+      builtin: kmod - switch modprobe to builtin
+      rules: do not preprocess 80-drivers.rules + 75-probe_mtd.rules
+      builtin: apply format string
+      remove last sbindir use
+      update NEWS
+      autogen.sh: moce CFLAGS from to configure.ac; print common ./configure options
+      builtin: kmod - link against libkmod
+      add copyright
+      builtin: kmod - reload index when rules are reloaded
+      builtin: rename load()/unload() to init()/exit()
+      invalidate rules and kmod index with 'udevadm control --reload'
+      update NEWS
+      builtin: firmware - move 'firmware' tool to builtins
+      builtin: firmware - add missing file
+      builtin: kmod - hook up udev main logging to libkmod
+      make: introduce --with-rootprefix=
+      update NEWS
+      move rules dirs to udev context; replace inotify with time-controlled stat()
+      udevd: always create runtime dir
+      builtin: move usb-db, pci-db to builtins
+      builtin: kmod - switch to kmod_module_probe_insert_module()
+      udevd: remove TIMEOUT= handling
+      update README
+      systemd: rename PassCred= to PsssCredentials=
+      remove mknod() logic and rely on 'devtmpfs'
+      builtin: kmod - hook up kmod_validate_resources()
+      build-sys: use use ${ac_default_prefix}
+      require kmod >= 3
+      build-sys: use --libexecdir=/usr/lib instead of /usr/lib/udev
+      autogen.sh: enable git pre-commit
+      merge udev/, libudev/, systemd/ files in src/; move extras/ to src/
+      replace unpacked sysfs test tree 'test/sys/' with packed tarball
+      rules: delete arch specific rules
+      doc: fix out of tree build (copy from libkmod)
+      autogen.sh: add CFLAGS and print entire line, so that mouse copy/paste works
+      build-sys: try to build without installed xsltproc
+      add test/src to .gitignore
+      tabs are as useful as a hole in the head
+      autogen.sh: makedev() misteriously breaks with -O0 here, use -O1 for now
+      fix debug message
+      add .vimrc
+      cdrom_id: int -> bool
+      fix compiler warning
+      man: mention that no daemons should be started by udev
+
+Lucas De Marchi (1):
+      builtin: kmod - log if modules are blacklisted
+
+Luis Felipe Strano Moraes (1):
+      Switch spawn_read to void and remove useless stores there.
+
+Martin Pitt (1):
+      75-persistent-net-generator.rules: Add Xen
+
+Mike Frysinger (1):
+      hwdb: drop useless line freeing
+
+Sjoerd Simons (1):
+      keymap: Add Lenovo Thinkpad X220 Tablet
+
+Ville Skyttä (1):
+      man: spelling fix
+
+
+Summary of changes from v174 to v175
+============================================
+
+David Zeuthen (2):
+      gudev: Use strtoul to parse unsigned 64-bit integers
+      gudev: Use g_ascii_strtoull() instead of strtoul()
+
+Harald Hoyer (1):
+      extras/keymap/findkeyboards: beautify shell code and get rid of grep
+
+Jerone Young (1):
+      keymap: Fix micmute remap for Lenovo Thinkpads
+
+Kay Sievers (7):
+      make: add gpg signing bits
+      ignore entire rules line if unknown keys are used
+      do not skip /dev/{disk,char}/M:m removal when the device node is already gone
+      replace AC_DISABLE_STATIC with LT_INIT([disable-static])
+      make: tweak some autofoo according to Flameeyes' recommendations for libabc
+      rules: restore rule to set cdrom group for optical drives
+      rules: fix typo
+
+Martin Pitt (8):
+      check-keymaps.sh: Allow running separately
+      extras/keymap/findkeyboards: Filter out non-event devices
+      findkeyboards: Consistently use spaces instead of tabs
+      keymap: Fix stuck keys on GIGABYTE i1520M
+      keymap: More Asus module variants
+      keymap: Fix "internet" key on HP G62
+      keymap: Fix bluetooth key on Acer TravelMate 7720
+      keymap: Fix stuck keys on BenQ nScreen
+
+
+Summary of changes from v173 to v174
+============================================
+
+David Zeuthen (1):
+      ata_id: Check for Compact Flash card
+
+Jerone Young (1):
+      Add mic mute keycode support for Lenovo Thinkpad USB keyboard
+
+Kay Sievers (34):
+      gtk-doc: delete empty files
+      libudev: list - use binary search for list lookup
+      rules: move input_id to default rules
+      implement path_id, usb_id, input_id as built-in command
+      do not remove static nodes on module unload
+      rules: remove legacy rules for cdrom and usb printer
+      update TODO
+      preserve 'sticky bit' on 'add/change' events
+      libudev: util_get_sys_(subsystem,driver}() -> util_get_sys_core_link_value()
+      export USEC_INITIALIZED= and take timestamp on message receive time
+      libudev: udev_device_get_sysattr_value() return syspath of custom links
+      libudev: list - properly sort linked list not only the index
+      mknod: do not complain about existing node
+      update README
+      libudev: fix typo in documentation
+      rules: fuse: do not mount fusectl from udev rules
+      keymap: add genius keymap to Makefile
+      update NEWS
+      usb_id: can't use global variables when used as built-in
+      remove 'udevadm trigger --type=failed' and SYSFS, ID, BUS keys
+      libudev: export udev_util_encode_string()
+      update TODO
+      systemd: no not start udev in a container
+      systemd: no not start udev in a container
+      delete left-over files in extras/
+      systemd: update drop-in sd-daemon files
+      udevadm: control - use /run/udev/control socket instead of abstract namespace one
+      udevd: control - no not delete socket file when --daemon is used
+      udev_ctrl_cleanup()- accept NULL as argument
+      update NEWS
+      udevd: install into /lib/udev instead of /sbin
+      udevd: add missing braces
+      systemd: use ConditionCapability=CAP_MKNOD instead of ConditionVirtualization=!container
+      rules: do not load sg module
+
+Kir Kolyshkin (1):
+      keymap: add Genius SlimStar 320
+
+Martin Pitt (1):
+      keymap: Update Acer Aspire 5920g
+
+Matthias Clasen (1):
+      make: allow to pass ${ACLOCAL_FLAGS}
+
+Paul Fox (1):
+      keymap: update the OLPC keymap for correct function key behavior
+
+Petr Uzel (1):
+      udevadm: settle - return failure if unknown option is given
+
+Steve Langasek (1):
+      udevd: exit - process events before signals in worker
+
+Thomas Hood (2):
+      keymap: Support keymap overrides in /etc/udev/keymaps
+      keymap: Support for microphone mute button on ThinkPad X220 et al
+
+
+Summary of changes from v172 to v173
+============================================
+
+Allin Cottrell (1):
+      configure: allow to disable mtd_probe
+
+Kay Sievers (15):
+      make: fix 'make tar-sync'
+      udevd: use 'uptime' in debug timestamp
+      udevd: fix (recently) broken static node permission setting
+      rules: mount fuse filesystem only 'add'
+      udevadm: move udevadm command descriptions into their files
+      udev-acl: skip ACLs when systemd is running, disable by default
+      do not delete database when renaming netif, the db name does not change anymore
+      do not allow kernel properties to be set by udev rules
+      configure: reorder options
+      rules: input - do not create (broken) links for bluetooth devices
+      rules: serial - do not export ID_PORT, use ID_USB_INTERFACE_NUM
+      rules: sound - instead of ID_IFACE use standard ID_USB_INTERFACE_NUM
+      keymap: do not run usb_id for bluetooth devices
+      udevadm: trigger --type=failed - log deprecation warning
+      udevd: debug - put timestamp in []
+
+Martin Pitt (4):
+      gudev: Ship JavaScript examples
+      scsi_id: Ship README
+      Remove obsolete extras/scsi_id/scsi_id.config
+      keymap: Only run on key devices
+
+
+Summary of changes from v171 to v172
+============================================
+
+Bastien Nocera (3):
+      accelerometer: add orientation property
+      udev-acl: fix memleak
+      accelerometer: add documentation
+
+Harald Hoyer (2):
+      udevadm-*.c: return != 0, if unknown option given
+      udev/udevadm-monitor.c: fixed misplaced brace
+
+Kay Sievers (33):
+      rules: apply 'audio' group of the static snd/{seq,timer} nodes
+      Makefile: add tar-sync
+      rules: static_node - use 0660 if group is given to get the cigar
+      rule-syntax-check.py: use print()
+      make: use 'git tag'
+      rules: run input_id for main input devices too
+      update TODO
+      configure: add AC_CONFIG_AUX_DIR, AC_CONFIG_SRCDIR
+      cdrom_id: add tray lock and eject handling
+      rules: enable in-kernel media-presence polling
+      update TODO
+      delete mobile-action-modeswitch which has moved to usb_modeswitch
+      libudev: enumerate - scan /sys/module
+      rules: move polling rule above 'block' match
+      libudev: monitor - update doc
+      rules: set polling value only if it is disabled
+      libudev: device - fix udev_device_get_tags_list_entry() to always load database
+      rules: remove redundant MODE="0664" from lp rules
+      rules: fix wrong wildcard match, we always need a ':*' at the end
+      libudev: device - export udev_device_has_tag()
+      path_id: add missing '-' to tape suffix
+      path_id: add ID_PATH_TAG= to be used in udev tags
+      enforce valid TAG+= names
+      update TODO
+      libudev: device - add udev_device_has_tag() to libudev.h and gtk-doc
+      libudev: enumerate - add udev_enumerate_add_match_parent()
+      libudev: enumerate - include parent device itself with match_parent()
+      libudev: enumerate - clarify documentation
+      path_id: recognize ACPI parent devices
+      rules: input - call path_id for ACPI devices
+      udevadm: monitor - use uptime to match the kernel's timestamp
+      libudev: ctrl - move code to udev directory
+      update sd-daemon.[ch]
+
+Keshav P.R (1):
+      rules: support for gpt partition uuid/label
+
+Lee, Chun-Yi (1):
+      Support more MSI notebook by using asterisk on dmi vendor name
+
+Marco d'Itri (1):
+      Add missing commas to 95-keymap.rules
+
+Martin Pitt (3):
+      keymap: Add Microsoft Natural Keyboard
+      keymap: Add force-release quirk for Hannspree SN10.
+      keymap: Add slight name variations of Toshiba Satellites
+
+Peter Jones (1):
+      ata_id: show the error message when HDIO_GET_IDENTITY fails
+
+
+Summary of changes from v170 to v171
+============================================
+
+Kay Sievers (17):
+      libudev: export symbols explicitely and individually from C code not from separate file or prefix match
+      libudev: device - make a bunch of symbols static
+      systemd: Replace Requires= with Wants=, run trigger in parallel
+      systemd: sort trigger after socket
+      systemd: trigger - run after udev.service (for now)
+      systemd: set socket buffer size to 128 MB like udev has
+      update TODO
+      update TODO
+      libudev: monitor - use SOCK_NONBLOCK
+      systemd: split socket file
+      systemd: add missing socket files
+      rules: fix whitespace
+      rules: implement TAGS== match
+      libudev: enumerate - do not ignore other matches when add_match_tag() is used
+      rules: support substitutions in TAG=
+      path_id: allow to be asked about usb_devices not only usb_interfaces
+      systemd: run udev.service and udev-trigger.service in parallel
+
+Scott James Remnant (1):
+      configure: allow usb.ids location to be specified
+
+
+Summary of changes from v169 to v170
+============================================
+
+Kay Sievers (1):
+      libudev: ctrl - properly wait for incoming message after connect
+
+Michal Soltys (1):
+      configure.ac: fixes for rule_generator and modeswitch
+
+
+Summary of changes from v168 to v169
+============================================
+
+Kay Sievers (26):
+      simplify rules file overwrite logic
+      libudev: list - use bit flags for 'sort' and 'unique'
+      libudev: queue - _unref() should return the object
+      remove dead fstab_import files
+      hid2hci: prepare move to bluez package
+      set event timeout to 60 sec and settle timeout to 120
+      udevd: improve error message in case exec() fails
+      configure: allow to enable/disable extras individually
+      delete hid2hci which moved to the bluez tree
+      update TODO/NEWS
+      bump requirement to Linux kernel 2.6.32 and ARM 2.6.36
+      libudev: ctrl - log accept4() errors
+      update NEWS
+      update INSTALL, NEWS, configure comment, queue doc
+      update TODO
+      udevd: create queue file before daemonizing to reliably block 'settle'
+      udevd: remove left-over SIGALRM
+      gudev: silent gtk-doc warnings
+      cdrom_id: remove unused --export switch to silent gcc
+      libudev: queue - always rebuild queue file when nothing is queued anymore
+      libudev: device - use DEVMODE from kernel as the default mode
+      update TODO
+      Merge branch 'docs/udev.xml' of git://github.com/mfwitten/udev
+      udate TODO, NEWS, INSTALL
+      build: use --gc-sections, -fvisibility=hidden
+      udevadm: settle: wake up more often if --seq-start= or --exit-if-exists= is used
+
+Koen Kooi (1):
+      configure: reintroduce introspection flags to fix crosscompilation
+
+Michael Witten (36):
+      Docs: udev.xml: Offset daemon name with commas
+      Docs: udev.xml: Remove commas (and unnecessary repetition)
+      Docs: udev.xml: `are' -> `is'; the subject is `Access'
+      Docs: udev.xml: Use present tense
+      Docs: udev.xml: Clarification through proper wording
+      Docs: udev.xml: `,' -> `;'
+      Docs: udev.xml: `key value' -> `key-value'
+      Docs: udev.xml: `,' -> `:'
+      Docs: udev.xml: Use `assignment' consistently
+      Docs: udev.xml: `comma-separated' is a better description
+      Docs: udev.xml: Remove unnecessary repitition
+      Docs: udev.xml: Add a few more words for context
+      Docs: udev.xml: Use `unless' for clarity
+      Docs: udev.xml: Clarify PROGRAM key
+      Docs: udev.xml: `a shell style' -> `shell-style'
+      Docs: udev.xml: Clean `*' description
+      Docs: udev.xml: Clean character range description
+      Docs: udev.xml: Clean up description of NAME assignment key
+      Docs: udev.xml: Clean up description of SYMLINK assignment key
+      Docs: udev.xml: Clean up description of ENV assignment key
+      Docs: udev.xml: Clean up description of RUN assignment key
+      Docs: udev.xml: Clean up description of LABEL assignment key
+      Docs: udev.xml: Add missing `.'
+      Docs: udev.xml: `which' -> `content of which'
+      Docs: udev.xml: `commandline' -> `command line'
+      Docs: udev.xml: Clean up WAIT_FOR description
+      Docs: udev.xml: `a' -> `the'
+      Docs: udev.xml: Clean up introduction to substitutions.
+      Docs: udev.xml: Use normal sentence structure
+      Docs: udev.xml: Actually make a separate paragraph
+      Docs: udev.xml: Add comma
+      Docs: udev.xml: `char' -> `character'
+      Docs: udev.xml: `comma-separated' is a better description
+      Docs: udev.xml: Clarify through a change in word ordering
+      Docs: udev.xml: Improved word order
+      Docs: udev.xml: Fix dangling modifier
+
+Nix (1):
+      libudev: queue - accept NULL passed into udev_queue_export_cleanup()
+
+
+Summary of changes from v167 to v168
+============================================
+
+David Zeuthen (1):
+      Run ata_id on non-removable USB devices
+
+Harald Hoyer (1):
+      udevd: clarify worker exit status
+
+Kay Sievers (35):
+      version bump
+      systemd: let settle depend on trigger, do not block basic with trigger
+      selinux: do not label files in runtime dir
+      selinux: firmware - do not label files in runtime dir
+      udevadm: control - add --exit
+      trivial cleanups
+      udevd: log warning if /run is not writable
+      libudev: ctrl - fix refcounting in connection handling
+      udevadm: settle - watch queue file
+      libudev: bump revision
+      udevadm: info --cleanup-db
+      udevd: do not nice processes
+      "db_persist=" -> "db_persist"
+      udevd: move OOM disable into --daemon option
+      systemd: add OOMScoreAdjust=-1000
+      require explicit "db_persist" to exclude device info from --db-cleanup
+      udevd: get netlink socket from systemd
+      fix more warnings
+      libudev: ctrl, monitor - use SOCK_NONBLOCK
+      systemd: socket -> sockets
+      udevadm: monitor - use epoll
+      libudev: test - use epoll
+      udevadm:  test - use printf() instead of info() for non-debug output
+      use 'else if' in epoll event array loop
+      libudev: run_program() - select() -> epoll
+      udevd: ppoll() -> epoll + signalfd
+      Merge branch 'docs/README' of git://github.com/mfwitten/udev
+      timeout handling without alarm()
+      udevadm: settle - kill alarm()
+      udevd: netif rename - use ifindex for temporary name
+      udevd: always use udevd[] log prefix
+      udevd: rules files - accept empty or /dev/null links
+      udevd: log signal number when spawned processes fail
+      systemd: Reqires= -> Wants=udev.socket
+      udevd, udev-event: sync waitpid() error handling
+
+Lee, Chun-Yi (1):
+      Add rule for Acer Aspire One ZG8 to use acer-aspire_5720 keymap
+
+Leonid Antonenkov (1):
+      rule-generator: net - ignore Hyper-V virtual interfaces
+
+Martin Pitt (3):
+      Revert "Do not build extras with --disable-extras"
+      Avoid spinning up CD on pressing eject button
+      keymap: Another ID for Logitech Wave keyboard
+
+Michael Reed (1):
+      path_id: rework SAS device handling
+
+Michael Witten (12):
+      Docs: README: `to replace' -> `replacing'
+      Docs: README: `,' -> `;'
+      Docs: README: Clean up a sentence
+      Docs: README: Use present tense
+      Docs: README: Add missing `and'
+      Docs: README: Remove commas and use subjective mood
+      Docs: README: Clean up `udev extras' requirements
+      Docs: README: Clarify configuration of existing devices
+      Docs: README: `does never apply' -> `never applies'
+      Docs: README: Flip sentence structure to improve wording
+      Docs: README: `set up' is the verb; `setup' is a noun
+      Docs: README: Add a comma to offset the modifier
+
+Seth Forshee (1):
+      keymap: Support Dell Latitude XT2 tablet-mode navigation keys
+
+Thomas Egerer (1):
+      udevd: add 'N:' to optstring in getopt_long
+
+
+Summary of changes from v166 to v167
+============================================
+
+Andrey Borzenkov (1):
+      udev-acl: add /dev/sgX nodes for CD-ROM
+
+David Zeuthen (1):
+      cdrom_id: Don't ignore profiles when there is no media available
+
+Harald Hoyer (2):
+      cdrom_id: cd_media_toc() extend toc size to 65536
+      udev-acl/70-acl.rules: tag ID_REMOTE_CONTROL with acl
+
+Kay Sievers (29):
+      version bump
+      Merge branch 'master' of git+ssh://master.kernel.org/pub/scm/linux/hotplug/udev
+      v4l_id: kill the v4l1 ioctl
+      v4l_id: remove left-over variable
+      update some comments
+      test-libudev: add short options
+      libudev: udev_device_get_sysattr_list_entry() update
+      libudev: resolve ifindex in udev_device_new_from_id_filename()
+      libudev: bump minor version
+      udev-acl: move sg rule to optical drive rule
+      move /dev/.udev/ to /dev/.run/udev/ and convert old udev database at udevd startup
+      NEWS: clarify /dev/.run/ requirements
+      input_id: silent gcc warnings
+      fstab_import: disable build
+      systemd: remove deprecated udev-retry.service
+      fstab_import: remove from configure
+      update sd-daemon.[ch]
+      udevd: use facility == LOG_DAEMON when writing to /dev/kmsg
+      udevd: initialize fds, for proper close() on exit
+      use /run/udev/ if possible and fall back to /dev/.udev/
+      rules: run ata_id only on SPC-3 or later optical drives
+      systemd: bind udev control socket in systemd and split udev.service
+      systemd: use sockets.target not socket.target
+      man: remove trigger --type=failed handling
+      libudev: export udev_get_run_path()
+      libudev: docs - add udev_get_run_path()
+      libudev: make valgrind happy
+      systemd: do not enable udev-settle.service by default
+      systemd: udev.socket - disable implicit dependencies
+
+Kei Tokunaga (1):
+      udevadm: enumerate - update prev pointer properly
+
+Lee, Chun-Yi (2):
+      Remap Acer WMI touchpad toggle key to F21 used by X
+      Remap MSI Laptop touchpad on/off key to F22 and F23
+
+Martin Pitt (12):
+      60-persistent-input.rules: Support multiple interfaces
+      Only build v4l_id if V4L1 header file is available
+      60-persistent-input.rules: Do not create duplicate links
+      Fix building with --disable-extras
+      Do not build extras with --disable-extras
+      v4l_id: Drop videodev.h check again
+      keymap: Fix Acer Aspire 5920G media key
+      input_id: Consistently use tabs for indentation
+      input_id: Add some debugging output
+      input_id: Avoid memory overflow with too long capability masks
+      input_id: Cover key devices which only have KEY_* > 255
+      input_id: Rewrite debug logging to use standard udev info()
+
+Seth Forshee (1):
+      keymap: continue reading keymap after invalid scancodes
+
+Thomas Egerer (3):
+      libudev: allow to get list of all available sysfs attrs for a device
+      libudev: use sysfs attr ilist interface for attribute walk
+      udevadm: info - make attribute array static and const
+
+
+Summary of changes from v165 to v166
+============================================
+
+Chris Bagwell (1):
+      Remap Eee PC touchpad toggle key to F21 used by X
+
+Gerd Hoffmann (1):
+      extras: add rules for qemu guests
+
+Jürgen Kaiser (1):
+      keymap: Add Acer Aspire 8930
+
+Kay Sievers (7):
+      version bump
+      man: generate html pages for www.kernel.org
+      man: fix typo
+      make: fix qemu rules file name
+      extras: qemu - fix typo
+      ata_id: do not print empty serial numbers to avoid unwanted trailing '_'
+      update gitignore
+
+Martin Pitt (6):
+      keymap: Add Acer TravelMate C310
+      keymap: Update README.keymap.txt
+      keymap: Add Lenovo ThinkPad X201 tablet
+      keymap: Move reading of event in separate function
+      keymap: More robust state machine
+      keymap: Explain how to end the program
+
+Matthew Garrett (1):
+      keymap: Remove wlan from Dell
+
+
+Summary of changes from v164 to v165
+============================================
+
+Andy Whitcroft (1):
+      keymap: Add release quirks for two Zepto Znote models and AMILO Xi 2428
+
+Bastien Nocera (2):
+      keymap: Add force release for HP touchpad off
+      extras/keymap: Make touchpad buttons consistent
+
+David Henningsson (1):
+      Add ACLs for FFADO supported sound cards
+
+David Zeuthen (6):
+      ata_id: Support SG_IO version 4 interface
+      Run scsi_id and ata_id on the scsi_device object
+      Use ata_id, not scsi_id, on ATAPI devices
+      Add GUdevEnumerator type and Device.get_tags() method
+      Add g_udev_device_get_is_initialized() method
+      gudev: Add Device.get_usec_since_initialized
+
+Harald Hoyer (2):
+      udev-rules.c: change import property buffer to 16384 bytes
+      70-acl.rules: add ACLs for ID_PDA devices
+
+Jakub Wilk (1):
+      man: udev - workaraound -> workaround
+
+Jan Drzewiecki (1):
+      cdrom_id: Fix media state for unreadable DVDs
+
+Kay Sievers (19):
+      version bump
+      rules: 78-sound-card - remove specific hardware matches, they do not belong here
+      rules: drop OSS audio rule
+      rules: drop alsa jack-plug input devices
+      rules: revert bsg use until the event ordering problem is sorted out
+      libudev: do not overwrite path with readlink() call
+      udevadm: info - honor --export and --export-prefix for property query
+      udevadm: info - honor --export, --export-prefix=
+      udevd: use dev_t or netif ifindex as database key
+      udevd: always create /dev/{char,block}/$major:$minor
+      udevd: simplify udev database and fix DEVNAME handling
+      udevd: switch to common id_filename functions
+      udevd: write full database file for (unsupported) renamed device nodes
+      check ifindex > 0 instead of subsystem == "net"
+      libudev: enumerate - allow to filter-out not-already-initialized devices
+      libudev: fix renamed device nodes detection logic
+      libudev: record and export "age" of device record
+      gudev: bump minor version
+      update NEWS
+
+Martin Pitt (5):
+      keymap: Add Sony Vaio VGN71
+      keymap: Add some more Sony Vaio VGN-* models
+      Add ACL for media player USB devices
+      keymap: Fix struck Touchpad key on Dell Latitude E series
+      keymap: Fix struck Touchpad key on Dell Precision M series
+
+Michal Soltys (1):
+      udevd: create static nodes before /dev/null is needed
+
+
+Summary of changes from v163 to v164
+============================================
+
+David Zeuthen (1):
+      Install libgudev-1.0.so in prefix / instead of prefix /usr
+
+Harald Hoyer (1):
+      cdrom_id: request the drive profile features with a dynamic length
+
+Kay Sievers (4):
+      version bump
+      udevd: do not wrongly delay events for devices with swapped names
+      return proper error code in rename_netif()
+      libudev: return kernel provided devnode when asked before we handled any rules
+
+Martin Pitt (2):
+      keymap: Apply force-release rules to all Samsung models.
+      keymap: Add Toshiba Satellite U500
+
+
+Summary of changes from v162 to v163
+============================================
+
+David Zeuthen (2):
+      gudev: Deliver ::uevent signal in the thread-default main loop
+      Bump required GLib version to 2.22
+
+Hannes Reinecke (1):
+      scsi_id: export target port group
+
+Kay Sievers (5):
+      version bump
+      scsi_id: fix compiler warnings
+      systemd: hook into basic.target instead of sysinit.target
+      systemd: sort before basic.target
+      udevd: add sd-daemon.c
+
+Lee, Chun-Yi (1):
+      keymap: Add alternate MSI vendor name
+
+Martin Pitt (8):
+      keymap: Add Lenovo Y550
+      Clarify WAIT_FOR documentation
+      fix various syntax errors in rules
+      Add automatic rules syntax check
+      cdrom_id: Try reading the medium if all MMC commands fail
+      Revert "cdrom_id: Try reading the medium if all MMC commands fail"
+      cdrom_id: Fall back to CDROM_DRIVE_STATUS if all MMC commands fail
+      cdrom_id: Don't read beyond "last track" in TOC
+
+Torsten Schoenfeld (1):
+      gudev: add a few annotations that newer gobject-introspection versions demand
+
+
+Summary of changes from v161 to v162
+============================================
+
+David Woodhouse (1):
+      Add keymap for Lenovo IdeaPad S10-3
+
+Jan Drzewiecki (2):
+      cdrom_id: Drop MEDIA_SESSION_NEXT for DVD-RW-RO
+      cdrom_id: Fix DVD blank detection for sloppy firmware
+
+Kay Sievers (10):
+      init: update systemd service files
+      init: update systemd service files
+      init: add 'udev -' to description in systemd service files
+      udevd: add pid to kmsg logs
+      init: edit systemd service descriptions
+      version bump
+      udevd: remove unneeded credential passing from init_notify()
+      set SELinux context on 'add' but not on 'change' events
+      systemd: enable all udev services unconditionally
+      Revert "Add alternative KVM MAC address blacklist"
+
+Luca Tettamanti (1):
+      Add support for oom_score_adj
+
+Marco d'Itri (2):
+      udev-acl: do not mistake all SCSI "processor" devices for scanner
+      do not create persistent name rules for KVM network interfaces
+
+Martin Pitt (12):
+      cdrom_id: Add media status debugging
+      udev(7): Point out required extension, and remove some confusion
+      keymap: Add Onkyo PC
+      keymap: Add HP G60
+      keymap: Fix Sony VAIO VGN-SZ2HP/B
+      udev(7) manpage: Fix description of $attr
+      gudev: fix crash if netlink is not available
+      keymap: Fix Acer TravelMate 4720
+      cdrom_id: Fix DVD-RW media detection
+      Fix KVM MAC address range
+      do not create persistent name rules for VMWare network interfaces
+      Add alternative KVM MAC address blacklist
+
+Michael Forney (1):
+      Don't install systemd scripts with --without-systemdsystemunitdir
+
+Michal Soltys (1):
+      ChangeLog fix
+
+
+Summary of changes from v160 to v161
+============================================
+
+Fortunato Ventre (1):
+      keymap: Add force-release quirks for a lot more Samsung models
+
+Harald Hoyer (3):
+      udev-event.c: rename interface to <src>-<dest>, if <dest> taken
+      rule_generator/write_net_rules: prevent interface to be named "eth"
+      cdrom_id: READ TOC before READ DISC INFORMATION fixes qemu
+
+Jan Drzewiecki (5):
+      cdrom_id: Fix detection of reblanked DVD+RW and DVD-RAM
+      cdrom_id: Handle pre-MMC2 drives
+      cdrom_id: Also apply format check to DVD-RW
+      cdrom_id: No "next session" for "other" media state
+      cdrom_id: Fix state for fresh DVD-RW
+
+Jerone Young (1):
+      Fix volume keys not releasing on Mivvy G310
+
+Kay Sievers (12):
+      version bump
+      rules: remove firewire rules for deprecated drivers
+      udev-acl: update firewire matches to recent rule changes
+      libudev: bump minor so version after adding symbols
+      call util_delete_path() only when we actually deleted stuff
+      udev-acl: properly handle CK change events for root user
+      udev-acl: remove specific device matches from the rules file
+      fix broken "compile warning fix"
+      always log error when renaming a network interface fails
+      do not rename the database on device rename
+      cdrom_id: whitespace fix
+      cdrom_id: do not bail out when we can not read the TOC like for empty CDRW
+
+Marco d'Itri (3):
+      hid2hci: fix Logitech diNovo, MX5500 and other keyboards
+      log an error when a message from the wrong version of udevadm is ignored
+      hid2hci: fix for Logitech diNovo Edge keyboard
+
+Martin Pitt (1):
+      keymap: Generalize Samsung keymaps
+
+Michal Schmidt (1):
+      udev-acl: really fix ACL assignment in CK events
+
+Richard Hughes (1):
+      udev-acl: add DDC_DEVICE to the types that are managed
+
+Stefan Richter (1):
+      rules: add more FireWire IDs: Point Grey IIDC; AV/C + vendor unique
+
+Yin Kangkai (7):
+      udevadm: fix short options in getopt()
+      udevd: fix some memory leaks in error path
+      malloc()+memset() -> calloc()
+      udevd: fix short options in getopt()
+      udevd: fix unref'ing of device in error path
+      udevd: create static device links only when the target exists
+      udev: fix compile warning
+
+
+Summary of changes from v159 to v160
+============================================
+
+Harald Hoyer (2):
+      60-persistent-storage-tape: s/path_id.sh/path_id/
+      60-persistent-storage-tape.rules: make own by-path symlink for nst tapes
+
+Kay Sievers (4):
+      version bump
+      rules: tape - remove WAIT_FOR instruction and don't export BSG_DEV
+      allow final assignment for OPTIONS:="nowatch"
+      udevd: init_notify() fix abstract namespace name handling
+
+Lennart Poettering (1):
+      systemd: make service files readable by GKeyFile
+
+Martin Pitt (2):
+      keymap: Find alternate Lenovo module
+      keymap: Add Lenovo ThinkPad SL Series extra buttons
+
+
+Summary of changes from v158 to v159
+============================================
+
+Jerone Young (1):
+      Fix stuck volume key presses for Toshiba Satellite U300 & U305models
+
+Kay Sievers (5):
+      version bump
+      add systemd service files
+      make: pre-process and install systemd service files when needed
+      make: fix 'make distcheck'
+      switch a few left-over from GPLv2 to GPLv2 or later
+
+Lennart Poettering (1):
+      systemd: update service files for newly introduced DefaultDependencies= option
+
+Martin Pitt (1):
+      keymap: Add Logitech Cordless Wave Pro
+
+Matthew Garrett (1):
+      keymap: Add support for IBM-branded USB devices
+
+Michael Meeks (1):
+      gudev: respect possibly given LD_LIBRARY_PATH
+
+Ryan Harper (2):
+      Add virtio-blk support to path_id
+      Add virtio-blk by-id rules based on 'serial' attribute
+
+
+Summary of changes from v157 to v158
+============================================
+
+Harald Hoyer (1):
+      extras/keymap: add Samsung N210 to keymap rules
+
+Kay Sievers (7):
+      version bump
+      libudev: fix fd leak in udev_enumerate_scan_devices() when tags are searched
+      udevd: in case we don't daemonize, send READY message to /sbin/init
+      delete last distro specific rules
+      remove a few comments in file headers
+      mtd_probe: add needed include, modprobe blacklist flag, and change some whitespace
+      rules: remove unused subdir
+
+Martin Pitt (4):
+      Fix hid2hci rules harder
+      add Vala vapi for gudev-1.0
+      Revert "add Vala vapi for gudev-1.0"
+      Fix usb printer rule for multiple USB interfaces
+
+Maxim Levitsky (1):
+      mtd_probe: add autodetection for xD cards
+
+Paul Bender (1):
+      configure.ac: fix cross compilation
+
+
+Summary of changes from v156 to v157
+============================================
+
+Harald Hoyer (1):
+      40-redhat.rules: removed file
+
+Jerone Young (3):
+      Fix wlan key on Inspirion 1210
+      Fix wlan key on Inspiron 910
+      Fix wlan key on Inspiron 1010 & 1110
+
+Kay Sievers (25):
+      configure.ac: version bump
+      Makefile.am: silent build mkdir
+      rules: mount fuse control filesystem
+      fix compilation with --enable-debug
+      while (1) -> for (;;)
+      childs -> children
+      udevd: replace --debug-trace with --children-max
+      udevd: fix comments
+      rules: add -v to modprobe calls to be able see what will be loaded
+      udevd:  read debug settings from kernel commandline
+      update NEWS
+      rules: delete pilot rules and remove redhat directory
+      man: add static device nodes and udevd debug options
+      man: add kernel command line parameters
+      man: udevd - update intro
+      rules: rename packages -> arch
+      rules: SUSE - move last distro rule to package
+      rules: add misc/30-kernel-compat.rules
+      make: mkdir /lib/udev/devices/
+      make: fix rules/ subdir names
+      udevd: set umask before creating files/directories
+      add IMPORT{cmdline}
+      IMPORT{cmdline}: start at first char after '='
+      libudev: doc - fix typo
+      update NEWS
+
+
+Summary of changes from v155 to v156
+============================================
+
+Bryan Kadzban (1):
+      udevd: fix typo /proc/fd -> /proc/self/fd
+
+Kay Sievers (4):
+      configure.ac: version bump
+      cdrom_id: do not export ID_CDROM_MEDIA_SESSION_LAST_OFFSET= for single session media
+      rules: optical drives - use ID_CDROM_MEDIA_TRACK_COUNT_DATA
+      libudev: fix udev_queue_get_seqnum_sequence_is_finished() with empty queue file
+
+
+Summary of changes from v154 to v155
+============================================
+
+Kay Sievers (11):
+      reset process priority before executing RUN+=
+      configure.ac: version bump
+      rules: SUSE - delete device-mapper rules
+      libudev: add O_CLOEXEC
+      use default mode of 0600 for nodes if gid == 0
+      udevd: create standard symlinks and handle /lib/udev/devices
+      update NEWS README
+      fix tests and allow MODE=000
+      create static nodes provided by kernel modules to allow module autoloading
+      update NEWS
+      man: directly use 'refentry'
+
+
+Summary of changes from v153 to v154
+============================================
+
+Harald Hoyer (2):
+      Makefile.am: add LGPL COPYING file to EXTRA_DIST
+      cdrom_id: only mark sr[0-9]* as ID_CDROM
+
+Jerone Young (1):
+      Fix volume keys not releasing for Pegatron platform
+
+Kay Sievers (23):
+      configure.ac: version bump
+      more readlink buffer size handling
+      remove left-over from ignore_remove and all_partitions
+      fix previous commit
+      udevadm: info --export-db -- remove watch handle export
+      add TAG= to improve event filtering and device enumeration
+      all to match against a given TAG==
+      udev-acl: use a tag instead of a property to mark devices
+      fix logic on-demand loading logic for db and uevent
+      use the usual TAG+=, TAG= logic
+      delete old tags when configuration changes
+      libudev: accept NULL in udev_device_get_tags_list_entry()
+      export tag functions
+      export udev_device_get_tags_list_entry()
+      udevd: always try to find an idle worker instead of forking a new one
+      remove unused parameter from udev_node_mknod()
+      remove debug output during rules parsing
+      warn when renaming kernel-provided nodes instead of adding symlinks
+      man: udevadm trigger - the default is "change" not "add"
+      update README regarding kernel version and default rules
+      add info message when empty NAME is given
+      libudev: add documentation for recently added functions
+      udevd: reload config only for *.rules files
+
+Martin Pitt (1):
+      keymap: Fix Bluetooth key on Acer TravelMate 4720
+
+Mathias Nyman (1):
+      remove buffer-overrun risk in readlink call
+
+Matthias Schwarzott (1):
+      rules: Gentoo - remove old devfs compat rules
+
+Michael Thayer (1):
+      fix device node deletion
+
+Robby Workman (1):
+      configure.ac: move firmware-path setting out of extras section
+
+Yin Kangkai (2):
+      keymap: Add keymap and force-release quirk for Samsung N128
+      keymap: Add keymap quirk of WebCam key for MSI netbooks.
+
+
+Summary of changes from v152 to v153
+============================================
+
+Kay Sievers (1):
+      configure.ac: version bump
+
+Robby Workman (1):
+      configure.ac: fix broken firmware search path in configure.ac
+
+
+Summary of changes from v151 to v152
+============================================
+
+Adrian Bunk (1):
+      udev needs automake 1.10
+
+Amit Shah (2):
+      Fix virtio-ports rule to use $attr instead of $ATTR
+      rules: virtio - fix is to check if the 'name' attribute is present
+
+Andy Whitcroft (2):
+      keymap: Add Samsung Q210/P210 force-release quirk
+      keymap: Add Fujitsu Amilo 1848+u  force-release quirk
+
+Dan Williams (1):
+      modeswitch: morph into tool that only switches Mobile Action cables
+
+David Zeuthen (3):
+      Decrease buffer size when advancing past NUL byte
+      Use UTIL_LINE_SIZE, not UTIL_PATH_SIZE to truncate properties
+      Increase UTIL_LINE_SIZE from 2048 to 16384
+
+Harald Hoyer (1):
+      cdrom_id: remove debugging code
+
+Jerone Young (6):
+      Force key release for volume keys on Dell Studio 1557
+      Fix Keymapping for upcoming Dell Laptops
+      Add new Dell touchpad keycode
+      Revert special casing 0xD8 to latitude XT only
+      Fix Dell Studio 1558 volume keys not releasing
+      Add support for another Dell touchpad toggle key
+
+Kamal Mostafa (3):
+      keymap: Unite laptop models needing common volume-key release quirk
+      keymap: Add force-release quirk for Coolbox QBook 270-02
+      keymap: Add force-release quirk for Mitac 8050QDA
+
+Kay Sievers (43):
+      libudev: bump minor version
+      udevadm: fix untested and broken commit to set buffer size
+      configure.ac: version bump
+      udev-acl: no not encourage use of ACL_MANAGE outside of rules file
+      replace utimes() with utimensat()
+      libbudev-private: rename udev_list_entry_get_flag()
+      udevadm: monitor - use / as separator in --subsystem-match=subsystem[/devtype]
+      use major:minor as entries in symlink stack instead of devpath
+      use major:minor as entries in watch directory
+      libudev: docs - .gitignore backup files
+      firmware: fix possible segfault when firmware device goes away while loading
+      do not reset SELinux context when the node was not touched
+      libudev: add udev_device_new_from_environment()
+      add LGPL COPYING to libudev and GUdev
+      cdrom_id: open non-mounted optical media with O_EXCL
+      libudev: update documentation
+      extras: mobile-action-modeswitch - update gitignore
+      scsi_id: add rand() in retry loop
+      cdrom_id: retry to open the device, if EBUSY
+      cdrom_id: check mount state in retry loop
+      cdrom_id: always set ID_CDROM regardless if we can run cdrom_id
+      rules: delete outdated packagees rules
+      rules: we do not have static devices which are renamed
+      unify/cleanup event handling
+      allow IMPORT{db}="KEY"
+      usb-db: remove double '/'
+      replace "add|change" with "!remove"
+      update NEWS
+      log info only if we actually delete the node
+      udevadm: trigger - switch default action from "add" to "change"
+      remove "all_partitions" option
+      rules: call modprobe on all events but "remove"
+      remove "ignore_remove" option
+      update NEWS
+      cdrom_id: rework feature/profiles buffer parsing
+      cdrom_id: print more debug messages
+      cdrom_id: debug - print feature values in hex
+      cdrom_id: debug - print feature values in hex
+      cdrom_id: set ID_CDROM_MEDIA=1 only for known media
+      Revert "Fix switching Logitech bluetooth adapters into hci mode."
+      add O_NOFOLLOW when creating files in link stack
+      delete only device nodes, not symlinks when deleting a devtmpfs node
+      doc: add section about how *not* to rename device nodes
+
+Marco d'Itri (3):
+      rules: input - create by-path/ links for pci devices
+      Fix switching Logitech bluetooth adapters into hci mode.
+      doc: document the WAIT_FOR timeout
+
+Martin Pitt (12):
+      keymap: Add Dell Inspiron 1011 (Mini 10)
+      Fix brightness keys on MSI Wind U-100
+      keymap: Fix LG X110
+      keymap: Add Toshiba Satellite M30X
+      udev-acl: Correctly handle ENV{ACL_MANAGE}==0
+      input_id: Fix linking
+      keymap: Add Acer TravelMate 6593G and Acer Aspire 1640
+      keymap: Fix another key for Acer TravelMate 6593
+      cdrom_id: Fix uninitialized variables
+      cdrom_id: Fix uninitialized buffers
+      cdrom_id: Do not ignore errors from scsi_cmd_run()
+      cdrom_id: Swap media state and TOC info probing
+
+Mike Brudevold (1):
+      cdrom_id: add missing profiles to feature_profiles
+
+Robert Hooker (1):
+      keymap: Add support for Gateway AOA110/AOA150 clones.
+
+Scott James Remnant (2):
+      libudev: export udev_monitor_set_receive_buffer_size()
+      udevadm monitor: increase netlink buffer size
+
+Thomas Bächler (1):
+      firmware: fix error reporting on missing firmware files
+
+Yury G. Kudryashov (3):
+      configure.ac - fix typo in --with-pci-ids-path option
+      hid2hci: include linux/types.h for __u32
+      configure.ac: ddd --with-firmware-path option
+
+
+Summary of changes from v150 to v151
+============================================
+
+Amit Shah (1):
+      rules: Add symlink rule for virtio ports
+
+Bryan Kadzban (1):
+      Fix reverted floppy-device permissions
+
+Egbert Eich (1):
+      rulews: suse - add do-not-load-KMS-modules rules
+
+Frederic Crozat (1):
+      rules: acl - add COLOR_MEASUREMENT_DEVICE match
+
+Kay Sievers (11):
+      configure.ac: version bump
+      udevd: inotify - do not parse rules at create but at close
+      do not remove device nodes of active kernel devices
+      libudev: device - create db file atomically
+      clarify message about not removed device node
+      input_id: include limits.h
+      keymap: include linux/limits.h
+      keymap: linux/input.h - get absolute include path from gcc
+      delete outdated and unmaintained writing_udev_rules
+      update README and NEWS
+      update tests
+
+Marco d'Itri (2):
+      writing_udev_rules: update rules files names
+      keymap: support for the Samsung N140 keyboard
+
+Martin Pitt (4):
+      add ACL rule for Garmin GPSMap 60
+      keymap: move force-release directory
+      extras/keymap/check-keymaps.sh: Ignore comment-only lines
+      keymap: Fix invalid map line
+
+
+Summary of changes from v149 to v150
+============================================
+
+Clemens Buchacher (2):
+      add Samsung R70/R71 keymap
+      keymap: Samsung R70/R71 force-release quirk
+
+Daniel Drake (2):
+      keymap: Add OLPC XO key mappings
+      keymap: Fix typo in compal rules
+
+Daniel Elstner (1):
+      libudev: wrap in extern "C" block for C++
+
+David Zeuthen (1):
+      Export ID_WWN_VENDOR_EXTENSION and ID_WWN_WITH_EXTENSION
+
+Jerone Young (1):
+      keymap: Lenovo Thinkpad USB Keyboard with Tracepoint
+
+Johannes Stezenbach (2):
+      keymap: add Samsung N130
+      keymap: handle atkbd force_release quirk
+
+Kay Sievers (15):
+      util_unlink_secure(): chmod() before chown()
+      floppy: fix rule to create additional floppy device nodes
+      configure.ac: version bump
+      remove remaining support for CONFIG_SYSFS_DEPRECATED
+      cdrom_id: remove deprecated device matches
+      rules: add "block" match to floppy rule
+      update mtime of nodes and links when we re-use them
+      udevadm: info - fix info --root --query=name --path= for device without a device node
+      remove remaining support for CONFIG_SYSFS_DEPRECATED
+      fix typo in log message priority handling
+      remove UDEV_RUN environment variable
+      udevadm: logging - copy va_list and do not use it twice
+      libudev: doc - add symbols to sections.txt
+      work around gtk-doc which breaks distcheck
+      gobject-introspection: use $datadir instead of $prefix
+
+Marco d'Itri (2):
+      build: keymap - create subdir
+      rules: udev-acl - add firewire video devices
+
+Martin Pitt (12):
+      keymap: Add Acer Aspire 1810T
+      95-keymap.rules: Run on change events, too
+      keymap: fix findkeyboards
+      Speed up udev_enumerate_scan_*
+      keymap: Add hotkey quirk for Acer Aspire One (AO531h/AO751h)
+      Clarify RUN/IMPORT documentation
+      keymap: Add Logitech S510 USB keyboard
+      keymap: add Acer TravelMate 8471
+      keymap: Add Acer Aspire 1810TZ
+      keymap: Add LG X110
+      keymap: Add Fujitsu Amilo Li 1718
+      keymap: Document force-release
+
+Piter PUNK (1):
+      firmware: convert shell script to C
+
+Scott James Remnant (1):
+      70-acl.rules: ACL manage Android G1 dev phones
+
+Thomas de Grenier de Latour (1):
+      libudev: enumerate - fix move_later logic
+
+
+Summary of changes from v148 to v149
+============================================
+
+Daniel Elstner (1):
+      really fix both in-tree and out-of-tree builds
+
+Dmitry Torokhov (1):
+      input-id: identify touchscreens
+
+Kay Sievers (4):
+      libudev: doc - use #NULL
+      configure.ac: version bump
+      really really fix both in-tree and out-of-tree builds
+      fix both in-tree and out-of-tree builds
+
+Martin Pitt (6):
+      input_id: Fix endless loop for non-input devices
+      input_id: Do not tag non-input devices with ID_INPUT
+      input_id: small optimization
+      input_id: check event mask
+      input_id: Check mouse button for ID_INPUT_MOUSE
+      udev_device_get_parent_with_subsystem_devtype(): Clarify documentation
+
+
+Summary of changes from v147 to v148
+============================================
+
+Dan Williams (3):
+      Revert "modem-modeswitch: add a device"
+      Revert "extras/modem-modeswitch: Add Huawei E1550 GSM modem"
+      modem-modeswitch: 61-option-modem-modeswitch.rules is only for Option NV devices
+
+Daniel Mierswa (1):
+      Fix typo in NEWS, ConsoleKit-0.4.11 -> 0.4.1
+
+David Zeuthen (4):
+      cdrom_id: Still check profiles even if there is no media
+      scsi_id: Export WWN and Unit Serial Number
+      Create /dev/disk/by-id/wwn-0x... symlinks
+      Also create /dev/disk/by-id/wwn-0x..-part%n symlinks for partitions
+
+Dmitry Torokhov (1):
+      extras/input_id: Correctly identify touchpads
+
+Harald Hoyer (1):
+      modem-modeswitch: add a device
+
+Kay Sievers (8):
+      rules: set mode of floppy device nodes to 0660
+      remove "ignore_device"
+      print warning for BUS=, SYSFS{}=, ID=
+      test-udev: remove "ignore_device" code
+      udev-test.pl: catch-up with recent changes
+      rules: remove support for IDE (hd*) devices
+      ata_id: skip ATA commands if we find an optical drive
+      Revert "Fix out-of-tree builds"
+
+Martin Pitt (5):
+      README.keymap.txt: small clarification
+      extras: Add input_id
+      70-acl.rules: Use new-style input properties
+      input: Deprecate ENV{ID_CLASS}
+      input_id: code cleanup
+
+Scott James Remnant (1):
+      Fix out-of-tree builds
+
+
+Summary of changes from v146 to v147
+============================================
+
+Alan Jenkins (1):
+      udevd: queue-export - remove retry loop
+
+Andrew Church (1):
+      fix wrong parameter size on ioctl FIONREAD
+
+Daniel Mierswa (2):
+      don't compare a non-existing function with NULL
+      use nanosleep() instead of usleep()
+
+David Zeuthen (4):
+      gudev: remove G_UDEV_API_IS_SUBJECT_TO_CHANGE since API is now stable
+      ata_id: export more advanced ATA features
+      gudev: Fix up GUdevDeviceNumber
+      gudev: Remove LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE from priv header
+
+Florian Zumbiehl (10):
+      util_delete_path(): use util_strscpy()
+      util_lookup_group(): fix memory leak if realloc() fails
+      util_delete_path(): handle multiple leading slashes
+      util_create_path(): fix possible out of bounds array access
+      ude_rules.c: fix possible NULL pointer dereference in get_key()
+      util_resolve_sys_link(): fix possible buffer overflow
+      udev_util_encode_string(): fix possible buffer overflow
+      udev-rules.c: parse_file() - fix possible buffer overflow
+      udev_queue_get_seqnum_sequence_is_finished(): fix possible file handle leak
+      util_run_program(): fix possible buffer overflow #2
+
+Harald Hoyer (2):
+      scsi_id: prevent buffer overflow in check_fill_0x83_prespc3()
+      rename interfaces to <iface>_rename if rename fails
+
+Jeremy Kerr (1):
+      util_run_program: restore signal mask before executing event RUN commands
+
+Kay Sievers (45):
+      make: sort Makefile.am per target/extra
+      configure.ac: version bump
+      udev-acl: allow to skip ACL handling
+      rules: rfkill has no group, so use 0644
+      rule_generator: net - fix MATCHDEVID
+      make: add comment
+      update NEWS
+      print warning for NAME="%k" - it breaks the kernel supplied DEVNAME
+      warn about non-readable or empty rules file
+      change database file names
+      assign errno for getgrnam_r()/getpwnam_r()
+      doc: udevadm test *does* create nodes and links these days
+      util_unlink_secure(): chmod() before chown()
+      util_create_path(): fix errno usage
+      inotify_add_watch(): do not store watch, if it failed
+      update TODO
+      update README
+      rules: suse - use NAME for mapper/control
+      libudev-util.c: get_sys_link() - return error for empty link target
+      udev-rules.c: remove 'first_token' variable
+      Revert "udev-rules.c: remove 'first_token' variable"
+      test: catch possible bug in GOTO resolving
+      udevadm: remove symlink support for old commands
+      util_run_program(): skip multiple spaces in argv creation
+      fix whitespace
+      require 2.6.27 for proper signalfd handling
+      fix randonm findings from llvm-clang-analyzer
+      simplify "symlink name stack"
+      reorder create_path() and node/link creation to be called in a direct sequence
+      put util_create_path() and file creastion in a retry loop
+      udevadm: control - remove compat code
+      scsi_id: delete copy of bsg.h
+      fix SYMLINK{} option parsing
+      rules: remove remaining NAME="%k"
+      rules: drop almost all NAME= keys
+      update TODO, NEWS
+      udevd: serialize events for with the same major/minor
+      break loops if util_create_path() returns error
+      remove "last_rule" option
+      use CLOEXEC flags instead of fcntl()
+      unblock signals we might want to handle
+      udevd: create /dev/.udev/rules.d/ before watching it wit inotify
+      gudev: fix pkg-config call to work with "make distcheck"
+      update NEWS
+      Revert "gudev: fix out-of-tree build"
+
+Lennart Poettering (5):
+      pci-db: make sure we actually read the pci.ids file instead of usb.ids
+      sound: recognize saa7134 TV card sound devices as TV cards
+      sound: include ALSA sound card id in ID_ID property
+      sound: include ALSA sound card id in /dev/snd/by-id/ links
+      Revert "sound: include ALSA sound card id in /dev/snd/by-id/ links"
+
+Marco d'Itri (6):
+      doc: writing_udev_rules updated for the new command names
+      rules: sound - do not use /usr/bin/env
+      udevadm: print all messages to stderr with priority higher or equal than LOG_ERR
+      udevadmi: control = exit with rc=2 if there is some system error
+      gudev: gir-scanner workaround for out of tree builds
+      gudev: fix out-of-tree build
+
+Mario Limonciello (1):
+      hid2hci: remove superfluous bmAttributes match
+
+Martin Pitt (24):
+      extras/keymap: Add Acer Aspire 6920
+      extras/modem-modeswitch: eject ZTE MF6xx fake CD-ROMs
+      extras/keymap: Fix hold key on Acer Aspire 6920
+      extras/keymap: Fix case matching for Micro-Star
+      Revert "extras/keymap: Fix case matching for Micro-Star"
+      make raw USB printer devices accessible for lp
+      modem-modeswitch rules: Match more devices
+      extras/keymap: fix hash table collisions
+      extras/keymap: Rename KEY_COFFEE to KEY_SCREENLOCK
+      fix single-session CD detection
+      fix previous commit for CD detection
+      make raw USB printer devices world-readable again
+      50-udev-default.rules: fix printer MODE
+      keymap: Add Logitech Wave USB
+      keymap: add missing map file
+      keymap: fix usb_id invocation
+      keymap: make USB keyboards really work
+      keymap: Add Logitech Wave cordless
+      keymap: add HP Pavillion dv6315ea
+      keymap: add HP 2230s
+      Makefile.am: fix build with mawk
+      extras/keymap/README.keymap.txt: Fix bug report link
+      fix major fd leak in link handling
+      modem-modeswitch: fix ZTE MF6xx rule
+
+Matthias Schwarzott (2):
+      rules: Gentoo update
+      rules: Gentoo update
+
+Maxim Levitsky (1):
+      keymap for Acer Aspire 5720
+
+Peter Rajnoha (1):
+      libudev: allow to store negative values in the udev database
+
+Scott James Remnant (1):
+      util_run_program: *really* restore signal mask before executing event RUN commands
+
+William Jon McCann (1):
+      udev-acl: catch up with ConsoleKit 0.4.1
+
+
+Summary of changes from v145 to v146
+============================================
+
+Alan Jenkins (3):
+      man: fix unused, inaccurate metadata
+      man: SYMLINK can be matched as well as assigned
+      fix spelling
+
+Anssi Hannula (2):
+      rules: exclude digitizers from joystick class
+      udev-acl: add joystick devices
+
+Diego Elio 'Flameeyes' Pettenò (21):
+      Merge libudev, udev, and the unconditional extras in a single Makefile.am.
+      Replace the custom test-run target with the standard make check.
+      Also merge into the top-level Makefile.am the simpler extras.
+      Change hook handling to be more portable.
+      Merge keymap building in the top-level Makefile.am.
+      Make keymap generation rules be silent (backward-compatible).
+      Move pkg-config docs and man pages before conditionals.
+      Finally, also merge gudev into the top-level Makefile.am.
+      Make sure to clean up all the built sources.
+      Make sure to use dependency/target variables.
+      Add silent-rule support for the gudev rules.
+      Fix building of introspection library on top-level Makefile.am.
+      Fix another relative path for the new working directory.
+      Include the correct directory for out-of-source builds.
+      Add tests to the distribution; this fixes "make distcheck".
+      Ask gperf to use ANSI-C for generation.
+      Merge in Makefile.am.inc into Makefile.am
+      Use the keymap check during “make distcheck” rather than “check”.
+      Fix building of documentation when doing out-of-source builds.
+      Fix “make distcheck” run outside of the source directory.
+      Use LT_INIT to explicit that udev needs libtool series 2.
+
+Eric W. Biederman (1):
+      fix util_lookup_group to handle large groups
+
+Erik Forsberg (1):
+      extras/modem-modeswitch: Add Huawei E1550 GSM modem
+
+Kay Sievers (18):
+      udevd: add timestamp to --debug output
+      v4l_id: exit with 0 when --help is given
+      configure.ac: version bump
+      hid2hci: remove hid structures and include kernel header
+      path_id: make global variable static
+      udevadm: trigger - add --sysname-match=
+      rules: serial - fix path_id call
+      path_id: fix typo in comment
+      format names are not case insensitive
+      hid2hci: rewrite (and break) rules and device handling
+      make: build internal tools against libudev-private.la
+      update a few years of copyright
+      libudev: silent gcc warning: may be used uninitialized in this function
+      make: suppress enter/leaving directory messages
+      re-enable failed event tracking
+      "record_failed" -> "fail_event_on_error"
+      udevd: block for 15 seconds after error when too old kernel is detected
+      make: fix issues from non-recursive conversion
+
+Lennart Poettering (1):
+      enumeration: move ALSA control devices to the end of the enumerated devices of each card
+
+Mario Limonciello (2):
+      hid2hci: support to hid2hci for recovering Dell BT devices after S3
+      hid2hci: install re-trigger for hid device when recovering from S3
+
+Martin Pitt (17):
+      add keymap for Clevo D410J laptop
+      extras/keymap: add Zepto ZNote
+      extras/keymap: add Everex Stepnote XT5000T
+      extras/keymap: add Compal Hel80i
+      keymap tool: improve help
+      keymap tool: support scancode/keycode pair arguments
+      keymap: inline one-line key maps
+      extras/keymap: fix check-keymaps.sh for inline mappings
+      extras/keymap: add recently added keymap files to Makefile.am
+      extras/keymap: Add HP Presario 2100
+      extras/keymap: cover more Compaq Evo models
+      extras/keymap: Add Fujitsu Amilo M
+      extras/keymap: teach findkeyboards about USB keyboards
+      extras/keymap: Add Samsung SX22S
+      extras/keymap: Fix crash for unknown keys
+      extras/keymap: Add Samsung NC20
+      extras/keymap: Fix Bluetooth key on Acer Aspire 6920
+
+
+Summary of changes from v144 to v145
+============================================
+
+Ian Campbell (1):
+      scsi_id: correct error handling in prepend_vendor_model
+
+Kay Sievers (10):
+      README: add CONFIG_BLK_DEV_BSG
+      use MIN() MAX() from param.h
+      configure.ac: version bump
+      libudev: device - free values before updating them
+      libudev: enumerate - sort with qsort()
+      udevd: detach event from worker if we kill a worker
+      udevadm: info - add space after R:, A:, W: on database export
+      udevd: make sure a worker finishes event handling before exiting
+      udevd: handle SIGCHLD before the worker event message
+      udevd: use bool
+
+
+Summary of changes from v143 to v144
+============================================
+
+Jon Masters (1):
+      firmware: search for third party or sysadmin supplied firmware updates
+
+Kay Sievers (19):
+      configure.ac: add AM_SILENT_RULES
+      configure.ac: version bump
+      TODO: add cleanup of ATA_COMPAT
+      libudev: queue - add comments for queue format
+      udev/.gitignore: add udev.pc
+      configure.ac: version bump
+      do not exports properties starting with a '.'
+      scsi_id: --reformat_serial - use udev_util_replace_whitespace()
+      ata_id: sync ID_SERIAL(_SHORT) with other *_id tools
+      rules: make ata_id properties the default for all ATA block devices
+      scsi_id: delete no longer needed config file
+      update NEWS
+      man: udev - add private properties like ENV{.FOO}="bar"
+      Merge branch 'firmware' of git://git.kernel.org/pub/scm/linux/kernel/git/jcm/udev-jcm
+      udevadm: test - print list of properties
+      build: do not delete .la files
+      libudev: monitor - handle kernel supplied DEVNAME properly
+      update NEWS
+      build: add *exec* to the internal rootlibdir name
+
+Martin Pitt (2):
+      hid2hci: narrow matches to real HCI devices
+      extras/udev-acl: add smartcard readers
+
+Stefan Richter (1):
+      rules: set group ownership of new firewire driver device files
+
+
+Summary of changes from v142 to v143
+============================================
+
+Alan Jenkins (5):
+      udevadm: settle - fix timeout
+      udevd: remove tiny bit of dead code
+      udevd: implement a more efficient queue file format
+      udev-selinux.c: remove libudev header
+      udevd: queue-export - fix crash
+
+Benjamin Gilbert (1):
+      test: check string substitutions in OWNER and GROUP
+
+Dan Williams (2):
+      rules: tty/net - move from udev-extras
+      extras/modem-modeswitch: move from udev-extras
+
+David Zeuthen (1):
+      gudev: move from udev-extras
+
+Kay Sievers (95):
+      version bump
+      rules: v4l do not mix vbi and video nodes
+      fix possible endless loop for GOTO to non-existent LABEL
+      Revert "rules: v4l do not mix vbi and video nodes"
+      rule-generator: cd - skip by-path links if we create by-id links
+      remove format char string truncation syntax
+      use more efficient string copying
+      edd_id: use openat()
+      use openat(), unlinkat(), fstatat()
+      update TODO
+      remove unused GL_FORMAT from rules parser
+      require key names in uppercase
+      keep the ifdef'd udevd testing/profiling hack
+      fix location of database files
+      udevadm: settle - make --timeout=0 working
+      update NEWS
+      rules: add SUBSYSTEM match to scsi rules
+      cdrom_id: suppress ID_CDROM_MEDIA_STATE=blank for plain non-writable CDROM media
+      udevadm: control - add comment to man page about --reload-rules
+      cdrom_id: add error message if open() fails
+      udevadm: settle - add --exit-if-exists=<file>
+      udevd: remove check for dev_t, DEVPATH_OLD takes care of that
+      str[sp]cpyl: add __attribute__ ((sentinel))
+      udevd: convert to event worker processes
+      udevd: close netlink socket in worker and set cloexec
+      rules: do not call path_id for virtual devices
+      udevd: use enum instead of char in struct declaration
+      allow format substitution in path of ATTR{<path>}=="<value>"
+      cleanup $attr{} substitution
+      path_id: implement in C using libudev
+      path_id: update SCSI handling
+      path_id: add comments
+      fix signed/unsigned warning
+      libudev: enumerate - allow multiple keys with the same name
+      udevadm: trigger - add --property-match=<key>:<value>
+      udevadm: info - accept --query without a value and print properties
+      udevadm: control - --env -> --property
+      udevadm: monitor --environment -> --property
+      path_id: handle fibre channel
+      path_id: add iscsi support
+      path_id: delete old shell script
+      udevd: print error if worker dies unexpectedly
+      path_id: rename scsi sub-fuctions
+      libudev: add comments to libudev.h
+      libudev: move to top-level directory
+      fix libudev include in Makefile.am.in
+      libudev: device_new() -> udev_device_new()
+      udevd: log info for created/killed workers
+      libudev: call log functions conditionally
+      move syslog wrapper to libudev
+      move common stuff from udev/ to private parts of libudev/
+      libudev: rename private files to *-private.c
+      rules: remove scsi ch module loading rule
+      update NEWS
+      udevadm: info -revert "accept --query without argument"
+      README: add kernel options
+      README: add INOTIFY and SIGNALFD
+      USE_LOG -> ENABLE_LOGGING, DEBUG -> ENABLE_DEBUG, USE_SELINUX -> WITH_SELINUX
+      libudev: add gtk-doc
+      libudev: update documentation
+      libudev: doc - add section headers
+      libudev: doc - add enumerate
+      libudev: doc - add queue
+      update TODO
+      libudev: doc - add namespace for index
+      libudev: move .so version to libudev Makefile
+      autogen.sh: simplify
+      TODO: update
+      libudev: remove prefix from .so version variables
+      libudev: doc - add empty libudev.types
+      udev-acl: move from udev-extras
+      INSTALL: add --enable-extras
+      udev-acl: handle missing action when called in CK mode
+      v4l_id: move from udev-extras
+      libudev: doc - libudev-docs.sgml -> libudev-doc.xml
+      gudev: fix typo in configure option
+      v4l_id: 70-v4l.rules -> 60-persistent-v4l.rules
+      configure: enable all extras by default, provide --disable-extras
+      autogen.sh: make "CFLAGS=-O0 ./autogen.sh" working
+      NEWS: add --disable-extras
+      cleanup ./configure installation directory options
+      rules: remove MMC rule, 2.6.30 has the modalias
+      configure.ac: print error if gperf is missing
+      libudev: install in $libdir and move later to $rootlibdir
+      extras/keymap: use LIBEXECDIR instead /lib/udev
+      README: add /lib/udev/ is private
+      rules: do not install usb-id/pci-id rules when --disable-extras is used
+      extras: delete man pages for private udev tools
+      README: update
+      extras/keymap: install findkeyboards in /lib/udev
+      INSTALL: use /sbin instead of %{sbindir}
+      NEWS: update
+      udev.pc: add
+      Merge branch 'master' of git+ssh://master.kernel.org/pub/scm/linux/hotplug/udev
+      docs: install writing_udev_rules
+
+Lennart Poettering (2):
+      rules: sound - move from udev-extra
+      usb-db: move from udev-extras
+
+Marcel Holtmann (1):
+      rules: make RFKILL control device world readable
+
+Mario Limonciello (1):
+      hid2hci: move from udev-extras
+
+Martin Pitt (5):
+      keymap: move from udev-extras
+      extras/keymap: Fix WLAN button on ThinkPads
+      keymap: Update findkeyboard path in docs
+      udev-acl: Manage hplip device permissions
+      extras/keymap: Update findkeyboards location
+
+Matthias Schwarzott (3):
+      rules: Gentoo update
+      rules: Gentoo update
+      rules: Gentoo update
+
+Scott James Remnant (1):
+      OWNER/GROUP: fix if logic
+
+
+Summary of changes from v141 to v142
+============================================
+
+Andre Przywara (1):
+      rules: create /dev/cpu/<n>/cpuid world readable
+
+Ian Campbell (1):
+      path_id: support identification of Xen virtual block devices
+
+John Wright (1):
+      edd_id: add cciss devices
+
+Kay Sievers (46):
+      version bump
+      libudev: path_encode - always return 0 if encoded string does not fit into size
+      libudev: monitor - clarify socket handling documentation
+      udevd: log error for too old kernels or CONFIG_SYSFS_DEPRECATED
+      rules: remove DVB shell script
+      update NEWS
+      cdrom_id: add Xen cdrom support
+      test-libudev: update monitor source
+      TODO: add packet filter
+      update NEWS
+      cdrom_id: add and use ID_CDROM_MEDIA to decide if we run vol_id
+      libudev: monitor - add client socket filter for subsystem value
+      udevadm: monitor - print error if we can not bind to socket
+      update TODO
+      udevadm monitor - add --subsystem-match=
+      libudev: monitor - use simpler hash
+      libudev: monitor - switch to filter_add_match_subsystem_devtype()
+      libudev: monitor - do not filter messages with wrong magic
+      udevadm: monitor - add <subsytem>:<devtype> support
+      libudev: monitor - add udev_monitor_filter_remove
+      libudev: queue - fix get_seqnum_is_finished()
+      cdrom_id: skip media tests if CDROM_DRIVE_STATUS != CDS_DISC_OK
+      libudev: queue - clarify comments
+      libudev: monitor - export filter_update()
+      update NEWS
+      drop "extern" keyword from non-static function
+      rule_generator: net - fix usb comment generation
+      rules: input - add links for USB/platform non-kbd/mouse devices
+      rules: input - fix comments
+      rules: add rfcomm* to group dialout
+      accept DEVNAME from the kernel as a hint for the node name
+      update TODO
+      build: use AC_MSG_RESULT
+      rules: add "event*" match
+      udevd: revert initial device node creation
+      rules: remove initramfs comment
+      handle devtmpfs nodes
+      oops, removed ppp entry from rules got committed
+      remove all PHYSDEVPATH handling and warning about
+      remove asmlinkage
+      rules: fix ieee1394 rules
+      add "static" back to the inline functions
+      update TODO
+      delete vol_id and require util-linux-ng's blkid
+      delete libvolume_id
+
+Lubomir Rintel (1):
+      rule-generator: net - whitelist NICs that violate MAC local scheme
+
+
+Summary of changes from v140 to v141
+============================================
+
+Adam Buchbinder (4):
+      usb_id: add manpage
+      cdrom_id: update manpage
+      create_floppy_devices: expand manpage
+      vol_id: fix language in manpage
+
+Alan Jenkins (1):
+      avoid leaking netlink socket fd to external programs
+
+Borislav Petkov (1):
+      rules: rename ide-floppy to ide-gd
+
+David Brownell (1):
+      rules: exclude mtd* from persistent disk links
+
+Kay Sievers (15):
+      rules: fix extra quote in 50-udev-default.rules
+      version bump
+      udevadm: test - handling trailing '/' in devpath
+      udevadm: monitor - clarify printed header
+      rules: remove ram* from persisten disk links blacklist
+      rules: serial - support ttyACM devices
+      rules: replace IDE driver with media match
+      usb_id: add ID_VENDOR_ID, ID_MODEL_ID, ID_USB_INTERFACE_NUM, ID_USB_DRIVER
+      libudev: GPL -> LGPL
+      usb_id: remove unused variable
+      send monitor events back to netlink socket
+      "UDEV_MONITOR_KERNEL/UDEV" -> "kernel/udev"
+      IMPORT: 2048 -> 4096 bytes buffer
+      path_encode: fix max length calculation
+      libudev: monitor - unify socket message handling
+
+Michal Soltys (1):
+      rules: md-raid.rules fix
+
+Robby Workman (1):
+      udevadm: trigger - add "--action" to --help
+
+Scott James Remnant (1):
+      libudev: monitor - ignore messages from unusual sources
+
+
+Summary of changes from v139 to v140
+============================================
+
+Harald Hoyer (1):
+      libvolume_id: bump age
+
+Kay Sievers (12):
+      version bump
+      update TODO
+      volume_id: ntfs - fix uuid setting
+      update TODO
+      rules: Fedora update
+      libudev: queue - use lstat() to check existence of symlink
+      udevadm: settle - add --seq-start= --seq-end=
+      udevd: switch watch symlinks to devpath
+      udevadm: add text for new options to command and man page
+      update TODO
+      libudev: ctrl - return error after sending ctrl message
+      udevadm: settle - use timeout signal, instead of loop counter
+
+Michael Prokop (1):
+      fix compile error in debug mode
+
+Scott James Remnant (1):
+      udevadm: settle - synchronise with the udev daemon
+
+
+Summary of changes from v138 to v139
+============================================
+
+Kay Sievers (11):
+      version bump
+      remove static local variable
+      use the event udev_device to disable the watch on "remove"
+      add "nowatch" to disable a default installed watch with a later rule
+      add m4/ subdir
+      use AC_USE_SYSTEM_EXTENSIONS instead of AC_GNU_SOURCE
+      usb_id: add ID_USB_INTERFACES=:0e0100:0e0200:010100:010200:
+      usb_id: return values if called directly for an usb_device
+      usb_id: fix NULL string usage
+      usb_id: fix comment
+      udevadm: info - export all devices with --export-db
+
+Scott James Remnant (10):
+      Don't add inotify watch until RUN rules processed.
+      Clear existing inotify watch before processing.
+      Cleanup a little.
+      Allow watch handle to be stored in the udevdb.
+      Store watch handle in db.
+      Use the udevdb to speed up watch clearing.
+      Put a log message in a more sensible place.
+      Output watch handle in udevadm info.
+      lookup the old watch handle; reload only if has a path
+      Look at more inotify events in the buffer than just the first.
+
+
+Summary of changes from v137 to v138
+============================================
+
+David Zeuthen (1):
+      *_id: add model/vendor enc strings
+
+Karel Zak (2):
+      vol_id: fix ddf version string
+      vol_id: add missing id->type to swap0
+
+Kay Sievers (13):
+      man: fix grammar
+      version bump
+      fix NAME="" logic
+      rules: dm - add escape for uuid links with whitespace
+      test: add test for empty and non-existent ATTR
+      rules: fix md "change"/"remove" handling
+      autogen.sh: add more warnings
+      fix NAME= and OPTION+="string_escape=..." logic
+      rules: move OPTIONS to separate rule
+      use global "reload_config" flag
+      rules: add "watch" option to dm and md rules
+      rules: include loop block devices in persistent links
+      release 138
+
+Matthias Schwarzott (1):
+      rules: Gentoo update
+
+Miklos Vajna (1):
+      doc: writing udev rules - refer to 'udevadm info' instead of 'udevinfo'
+
+Scott James Remnant (2):
+      udevd: optionally watch device nodes with inotify
+      rules: update persistent storage rules to use inotify watches
+
+
+Summary of changes from v136 to v137
+============================================
+
+Alan Jenkins (2):
+      man: typo fixes
+      remove stray initializer
+
+Kay Sievers (17):
+      version bump
+      rules: fix typo in ide cd rule
+      libudev: use 4096 bytes buffer for attribute reading
+      rules: add drm devices to group "video"
+      do not complain about a missing /etc/udev/rules.d/
+      udevadm: test - remove --force option
+      update NEWS
+      remove name from index if the node name has changed
+      cleanup old names before creating the new names
+      open-code pollfd setup
+      increase netif renaming timeout from 30 to 90 seconds
+      Merge commit '5f03ed8a56d308af72db8a48ab66ed68667af2c6'
+      Merge commit '9032f119f07ad3b5116b3d4858816d851d4127de'
+      split up long line
+      udevd: add back SA_RESTART
+      usb_id: handle ATAPI devices like SCSI devices
+      udevadm: settle - fix typo
+
+Lennart Poettering (1):
+      fix naming for tape nst devices in /dev/tape/by-path/
+
+Olaf Kirch (2):
+      udevd: use ppoll instead of signal pipes
+      reap children faster
+
+Scott James Remnant (2):
+      Allow user and group lookup to be disabled.
+      Expose delayed name resolution
+
+Sven Jost (1):
+      volume_id: support via raid version 2
+
+
+Summary of changes from v135 to v136
+============================================
+
+Adam Buchbinder (1):
+      extras: fix mis-spelling of "environment"
+
+Harald Hoyer (1):
+      rule_generator: fix enumeration for write_cd_rules
+
+Jeremy Higdon (1):
+      path_id: rework SAS persistent names
+
+Karel Zak (1):
+      volume_id: HPFS code clean up
+
+Kay Sievers (54):
+      rules: ATA_COMPAT do not try to match on sr*, it will never have vendor ATA
+      scsi_id: do not fail if no serial is found like for optical drives
+      update configure and NEWS
+      rules: fix isdn rules
+      rules: add persistent /dev/serial/{by-id,by-path} rules
+      make: install serial rules file
+      make: do not delete autotools generated file with distclean
+      udevadm: settle - allow --timeout=0 and --quiet
+      rules: move aoe rules to default rules file
+      volume_id: btrfs - update format
+      rules: add "do not edit header"
+      volume_id: support sub-uuid's and plug in btrfs device uuid
+      libudev: include <sys/types.h>
+      build: add -lsepol
+      build: just use autoreconf -i
+      rules: remove ide-scsi
+      rules: first simple step merging with Ubuntu rules
+      "'/sbin/modprobe abnormal' exit" - also print program options
+      rules: more changes toward Ubuntu rules merge
+      rules: more changes toward Ubuntu rules merge
+      rules: remove /dev/raw/raxctl symlink, it's a devfs leftover
+      rules: rtc - create rtc compat link only for cmos type rtc
+      rules: remove legacy symlinks
+      rules: do not put raw1394 in "video" group
+      rules: second round merging with Ubuntu rules
+      rules: remove /dev/dsp /dev/audio
+      rules: put alsa in group "audio"
+      rules: isdn - remove /dev/isdn/capi20 symlink
+      rules: provide /dev/raw/rawctl
+      if needed, store database entries also for devices which do not have a device node
+      build: use autoreconf --symlink
+      usb_id: add "image" class
+      require non-SYSFS_DEPRECATED 2.6.20+ kernel
+      build: default to --prefix=/usr --exec-prefix=""
+      libudev: enumerate - add lookup by property
+      rules: input - make sure needed variables are set
+      libudev: device - read "uevent" only if info is not already loaded
+      libudev: subsytem -> subsystem
+      libudev: bump revision
+      usb_id: use devtype lookup
+      require 2.6.22+ kernel
+      rules: Ubuntu merge - use group "cdrom"
+      rules: Ubuntu merge - use group "tape"
+      rules: replace DVB shell script rule
+      rules: Ubuntu merge - s/uucp/dialout/
+      update NEWS
+      update NEWS
+      enable skipping of "naming-only" rules
+      usb_id: s/image/media/
+      udevadm: s/udevinfo/udevadm info/
+      rules: reorder block rules
+      rules: zaptel - add "dialout" group
+      libudev: device - add udev_device_get_property_value()
+      libudev: test - add udev_device_get_property_value()
+
+Marcel Holtmann (3):
+      libudev: device - add devtype support
+      libudev: device - lookup subsystem and devtype together
+      libudev: device - remove udev_device_get_parent_with_subsystem
+
+Michal Soltys (1):
+      man: udev - update NAME assignment
+
+Ryan Thomas (1):
+      rules: add rules for AoE devices
+
+
+Summary of changes from v134 to v135
+============================================
+
+Kay Sievers (6):
+      usb_id: add "break" to currently unused case labels
+      rules: fix cciss disk/by-id/ links
+      rules: add infiniband rules
+      rules: infiniband.rules -> 40-infiniband.rules
+      fix network interface name swapping
+      update configure and NEWS
+
+Marcel Holtmann (1):
+      usb_id: fix switch statement for video type
+
+Piter PUNK (2):
+      rules: /dev/null -> X0R
+      rules: add usb device nodes
+
+
+Summary of changes from v133 to v134
+============================================
+
+Gabor Z. Papp (1):
+      include errno.h in sysdeps.h
+
+Harald Hoyer (1):
+      rules: add persistent rules for memory stick block devices
+
+Kay Sievers (19):
+      autogen.sh: fix -print-multi-os-directory usage
+      volume_id: update btrfs magic
+      bump version
+      rules: merge group "video" into default rules
+      rules: v4l - add by-id/ links for USB devices
+      libudev: accept NULL whitelist in util_replace_chars()
+      usb_id: replace chars in returned strings
+      ata_id: make sure, we do not have slashes in values
+      scsi_id: make sure, we do not have slashes in values
+      volume_id: remove unused usage types
+      vol_id: if regular files are probed, use stat() for the size value
+      volume_id: update btrfs
+      volume_id: clear probing result before probing and do not probe a second time, if not needed
+      path_id: fix fibre channel handling
+      update NEWS TODO
+      floppy: use ARRAY_SIZE()
+      fix handling of swapping node name with symlink name
+      silence PHYSDEV* warning for WAIT_FOR* rules
+      rules: exclude "btibm" devices from vol_id calls
+
+Matthias Schwarzott (1):
+      rules: Gentoo update
+
+Peter Breitenlohner (2):
+      man: fix typos
+      floppy: fix array bounds check and minor calculation
+
+
+Summary of changes from v132 to v133
+============================================
+
+Alan Jenkins (2):
+      udevd: de-duplicate strings in rules
+      scsi_id: we don't use DEVPATH env var anymore, update man page
+
+Karel Zak (1):
+      volume_id: fat - move check for msdos signature (0x55 0xaa)
+
+Kay Sievers (22):
+      silence "comparison between signed and unsigned"
+      string index - split nodes and childs to allow and unlimited number of childs
+      reserve child slot 0
+      merge trie nodes, childs and root into a single array
+      set errno = ENOSYS in inotify stub
+      udevadm: info - unify -V and --version
+      rules: remove DEVTYPE disk/partition
+      rules: remove pnp shell script, acpi loads these modules properly
+      update NEWS
+      configure: add linux-hotplug mail address
+      remove len == 0 check, the index root is always '\0'
+      volume_id: bump revision
+      volume_id: always check for all filesystem types and skip conflicting results
+      volume_id: fat - accept empty FAT32 fsinfo signature
+      fix spelling in comment
+      volume_id: ntfs - mark as no other fs must match
+      vol_id: clarify error message
+      libudev: device - handle disk "device" link for partitions in deprecated sysfs layout
+      limit $attr(<symlink>) magic to well-known links only
+      udevd: fix cleanup of /dev/.udev/uevent_seqnum
+      fix $links substitution for devices without any link
+      update NEWS
+
+Sergey Vlasov (1):
+      udevadm: fix option parsing breakage with klibc
+
+
+Summary of changes from v131 to v132
+============================================
+
+Kay Sievers (2):
+      fix size_t compiler warning on 32 bit platforms
+      convert debug string arrays to functions
+
+
+Summary of changes from v130 to v131
+============================================
+
+Alan Jenkins (17):
+      libudev: fix sysnum logic for digit-only device names
+      udevd: avoid overhead of calling rmdir on non-empty directories
+      use more appropriate alternatives to malloc()
+      libudev: util - optimize path_encode()
+      libudev: allocate udev_device->envp[] dynamically
+      replace strncpy() with strlcpy()
+      use re-entrant variants of getpwnam and getgrnam
+      udevd: fix memory leak
+      udevd: fix WAIT_FOR_SYSFS execution order
+      fix handling of string_escape option
+      udevd: use a tighter loop for compare_devpath()
+      udevd: avoid implicit memset in match_attr()
+      kerneldoc comment fixes
+      udevd: simplify rules execution loop
+      udevd: fix termination of rule execution
+      udevd: be more careful when matching against parents
+      udevd: shrink struct token to 12 bytes
+
+Kay Sievers (113):
+      remove outdated docs/README-gcov_for_udev
+      libudev: device - add device lookup by subsystem:sysname
+      libudev: also prefix non-exported functions with udev_*
+      libudev: add udev_monitor_send_device()
+      libudev: list - add flag
+      libudev: device - generate DEVNAME and DEVLINKS properties
+      vol_id: update README
+      libudev: handle ! in sysname, add sysnum, return allocated list_entry on add
+      delete simple-build-check.sh
+      test: move global ENV{ENV_KEY_TEST}="test" to local rule
+      libudev: monitor - fix send_device() property copying
+      libudev: device - add get_envp() to construct envp from property list
+      libudev: do not include ctrl in libudev.so
+      libudev: monitor - do not mangle DEVLINKS property
+      libudev: update DEVLINKS property when properties are read
+      libudev: device - lookup "subsystem" and "driver" only once
+      libudev: device - export properties when values are set
+      libudev: list - handle update of key with NULL value
+      libudev: ctrl - fix typo in set_env()
+      libudev: add global property list
+      libudev: device - copy global properties, unset empty properties
+      volume_id: btrfs - update magic to latest disk format
+      udevd: use libudev
+      move udev_device_db to libudev
+      rename udev source files
+      libudev: always add UDEV_LOG
+      libudev: monitor - export MAJOR/MINOR only if available
+      udev-node: name_list -> udev_list
+      udev-rules-parse: name_list -> udev_list
+      delete name_list, move common file functions
+      fix sorting of rules files
+      run_program: prevent empty last argv entry
+      update IMPORT= file/stdout property parsing
+      update rules file parsing
+      delete udev-util-file.c
+      libudev: list - prepend udev_* to all functions
+      libudev: add sysnum to test program
+      test: fix a few unintentially wrongly written rules which cause parse errors
+      libudev: monitor - add set_receive_buffer_size()
+      libudev: ctrl - change magic to integer
+      libudev: make list_node functions available
+      udevd: use udev_list_node
+      collect: use udev_list
+      delete list.h
+      merge udev-rules.c and udev-rules-parse.c
+      make struct udev_rules opaque
+      move run_program to util
+      udev_event_run() -> udev_event_execute_rules()
+      udev_rules_run() -> udev_event_execute_run();
+      move udev_rules_apply_format() to udev-event.c
+      udev_list_cleanup() -> udev_list_cleanup_entries()
+      selinux_init(udev) -> udev_selinux_init(udev)
+      prefix udev-util.c functions with util_*
+      pass make distcheck
+      libudev: device - get_attr_value() -> get_sysattr_value()
+      cdrom_id: remove ARRAY_SIZE() declaration
+      replace missing get_attr_value() -> get_sysattr_value()
+      add "root" == 0 shortcuts to lookup_user/group()
+      do not use the new work-in-progress parser rule matcher
+      libudev: device - 128 -> ENVP_SIZE
+      add util_resolve_subsys_kernel()
+      handle numerical owner/group string in lookup_user/group()
+      replace in-memory rules array with match/action token list
+      do not create temporary node ($tempnode) if node already exists
+      shrink struct udev_event
+      shrink struct udev_event
+      rule_generator: fix netif NAME= value extraction regex
+      skip SYMLINK rules for devices without a device node
+      rules: let empty strings added to buffer always return offset 0
+      fix uninitialized variable warnings
+      cache uid/gid during rule parsing
+      distinguish "match" from "assign" by (op < OP_MATCH_MAX)
+      determine at rule parse time if we need to call fnmatch()
+      special-case "?*" match to skip fnmatch()
+      libudev: monitor - replace far too expensive snprintf() with strlcpy()
+      libudev: monitor - cache result of monitor send buffer
+      fix "unused" warnings
+      remove debug printf
+      match KEY="A|B" without temporary string copy
+      match_attr() - copy attr value only when needed
+      do not init string arrays, just clear first byte
+      fix $attr{[<subsystem>/<sysname>]<attribute>} substitution
+      libudev: device - fill envp array while composing monitor buffer
+      test: add RUN+="socket: ..." to a test to run monitor code
+      libudev: device - allocate envp array only once
+      update NEWS
+      udevd: merge exec and run queue to minimize devpath string compares
+      ATTR{}== always fails if the attribute does not exist
+      rules: remove SCSI timeouts
+      rules: remove "add" match from usb device node rule
+      edd_id: add "change" event match
+      fstab_import: add "change" event match
+      write trace log to stderr
+      log rules file and line number when NAME, SYMLINK, OWNER, GROUP, MODE, RUN is applied
+      skip entire rule containing device naming keys, if no device can be named
+      fix udev_node_update_old_links() logic
+      move some info() to dbg()
+      add "devel" and "install" switches to autogen.sh
+      move debugging strings inside #ifdef DEBUG
+      firmware.sh: record missing files in /dev/.udev/firmware-missing/
+      fix list handling in enumerate and rules file sorting
+      volume_id: btrfs update
+      info() PROGRAM and IMPORT execution
+      fix $links substitution
+      fix cleanup of possible left-over symlinks
+      do not import the "uevent" file when we only read the db to get old symlinks
+      usb_id: MassStorage SubClass 6 is "scsi" not "disk"
+      unify string replacement
+      $links should be relative
+      fix indentation
+      rules: md - add mdadm 3 device naming
+      cleanup /dev/.udev/queue on startup and exit
+      udevadm: settle - exit if udevd exits
+
+Matthias Koenig (1):
+      volume_id: swap - larger PAGE_SIZE support
+
+Steven Whitehouse (1):
+      volume_id: support for GFS2 UUIDs
+
+
+Summary of changes from v129 to v130
+============================================
+
+Kay Sievers (26):
+      fix compile error with --disable-logging
+      libudev: enumerate - add_device() -> add_syspath()
+      volume_id: hpfs - read label and uuid
+      use no_argument, required_argument, optional_argument in longopts
+      libudev: get rid of selinux
+      libudev: device - add get_parent_with_subsystem()
+      usb_id: use libudev
+      udevadm: info - fix --query=all for devices without a device node
+      vol_id: add size= option
+      move selinux noops to udev.h
+      volume_id: add dbg() as noop to check for compile errors
+      vol_id: fix logging glue
+      vol_id: always use the safe string versions for unencoded label and uuid
+      volume_id: better DDF raid detection
+      volume_id: add btrfs
+      volume_id: use PRIu64i, PRIx64 macros
+      udevd: clarify deprecated sysfs layout warning
+      libudev: fix --enable-debug
+      don not print error if GOTO jumps just to next rule
+      volume_id: add more vfat debugging information
+      libudev: libudev.pc remove selinux
+      store node name and symlinks into db symlink target if they are small enough
+      volume_id: more fat debugging
+      libudev: fix typo in "multiple entries in symlink" handling
+      connect /sys and /dev with /sys/dev/{block,char}/<maj>:<min> and /dev/{block,char}/<maj>:<min>
+      replace spaces in dm and md name symlinks
+
+
+Summary of changes from v128 to v129
+============================================
+
+Alan Jenkins (7):
+      udev-test.pl: set non-zero exitcode if tests fail
+      scsi_id: compiler warning on 32-bit
+      trivial cleanup in udev_rules_iter
+      avoid repeated scans for goto targets (udev_iter_find_label)
+      replace strerror() usage with threadsafe "%m" format string
+      fix messages (inc. debug compile failure) introduced when optimizing "goto"
+      allow compiler to check dbg() arguments on non-debug builds
+
+Kay Sievers (46):
+      libudev: switch to "udev_device_get_parent"
+      libudev: udev_device - add attribute cache
+      libudev: handle "device" link as parent, handle "class" "block" as "subsystem"
+      udevadm: info - fix lookup-by-name
+      libudev: switch API from devpath to syspath
+      libudev: rename ctrl_msg to ctrl_msg_wire
+      vol_id: fix lib logging glue
+      fix broken symlink resolving
+      fix udevadm trigger
+      libudev: pass udev_device in enumerate
+      libudev: fix "subsystem" value
+      always include config.h from Makefile
+      libudev: udev_device_get_devname -> udev_device_get_devnode
+      libudev: add udev_device_new_from_devnum()
+      libudev: also import "uevent" file when reading udev database
+      libudev: add userdata pointer
+      libudev: replace awkward callback list interfaces with list iterators
+      libudev: get devnum from uevent file
+      libudev: enumerate_get_devices_list -> enumerate_get_list
+      libudev: initialize selinux only when needed
+      libudev: device - read database only when needed
+      libudev: rework list handling
+      libudev: more list rework
+      lubudev: accept more sys directories as devices, and parent devices
+      libudev: enumerate - accept list of subsystems to scan, or skip
+      libudev: enumerate "subsystem"
+      libudev: enumerate - scan /sys/block/ if needed
+      libudev: enumerate - split new() and scan()
+      test: replace ancient sysfs tree with recent one
+      test: add missing pci directory because of .gitignore *.7
+      gitignore: move *.8 to subdirs
+      test: replace last reference of "/class/*" devpath
+      fix dbg() callers
+      libudev: enumerate - scan devices and subsystems, add subsystem and attribute filter
+      udevadm: trigger: use libudev
+      fix segfault caused by wrong pointer used in dbg()
+      libudev: device_init() -> device_new()
+      udevadm: trigger fix long option --type=
+      libudev: add queue interface
+      udevadm: settle - use libudev queue
+      libudev: device - handle /sys/block/<disk-device-link>/<partition>
+      libudev: enumerate - ignore regular files while scanning
+      udevadm: trigger --type=failed - use libudev queue
+      rules: ieee1394 - create both, by-id/scsi-* and by-id/ieee-* links
+      build: include Makefile.am.inc in all Makefile.am
+      udevd: print warning if CONFIG_SYSFS_DEPRECATED is used
+
+
+Summary of changes from v127 to v128
+============================================
+
+Alan Jenkins (8):
+      fix uninitialized name_list error::ignore_error
+      do not needlessly declare some local variables in udev_rules_parse.c as static
+      remove deprecated envp[] in main()
+      fix name compare bug name_list_key_add()
+      remove redundant string copy in udev_rules_apply_format()
+      remove redundant "remove trailing newlines" in udevadm info
+      threadsafe rules iteration
+      fix off-by-one in pass_env_to_socket()
+
+Kay Sievers (53):
+      libudev: add monitor documentation
+      libudev: fix --disable-log
+      autogen.sh: add --with-selinux
+      volume_id: hfs - calculate proper uuid
+      fix dangling pointer returned by attr_get_by_subsys_id()
+      udev-test.pl: add --valgrind option
+      libudev: libudev.pc add Libs.private
+      volume_id: fail on undefined __BYTE_ORDER
+      remove FAQ
+      libudev: fix monitor documentation
+      libudev: add udev_device_get_syspath()
+      udev_device_init() remove statically allocated device support
+      udevadm: info - fix broken --device-id-of-file=
+      udevadm: control - use getopt_long()
+      udevadm: print warning to stderr if udevadm is called by symlink
+      udev-test.pl: remove left-over comment from --valgrind option
+      udevadm: rename source files
+      udevadm: rename internal functions to udevadm_*
+      udevadm: split out control functions
+      udevadm: move init from commands to udevadm
+      autogen.sh: add debug
+      use libudev code, unify logging, pass udev context around everywhere
+      volume_id: linux_raid - fix logic for volumes with size == 0
+      vol_id: add --debug option
+      udevadm: add --version --help options to man page, hide them as commands
+      move udev_ctrl to libudev-private
+      udev-test.pl: set udev_log="err"
+      test-udev: cleanup libudev context and overridden rules file string
+      test-udev: remove unused var
+      add a bunch of private device properties to udev_device
+      udevadm: monitor - use libudev for udev monitor
+      libudev: monitor - add event properties to udev_device
+      udevadm: log message if udevadm link is used
+      udevd: remove max_childs_running logic
+      libudev: monitor- add netlink uevent support
+      udevadm: monitor - use libudev code to retrieve device data
+      libudev: udev_device - read "driver" value
+      libudev: rename enumerate function
+      libudev: add selinux
+      libudev: initialize selinux after logging
+      volume_id: merge util.h in libvolume_id-private.h
+      update file headers
+      libudev: udev_device - add more properties
+      libudev: do not use udev_db.c
+      libudev: get rid of udev_sysfs.c
+      libudev: get rid of udev_utils.c
+      libudev: rename libudev-utils.c libudev-util.c
+      libudev: do not use any udev source file
+      extras: use libudev code
+      convert to libudev and delete udev_utils_string.c
+      get rid of udev_sysdeps.c
+      use size definitions from libudev
+      udevadm: info - use "udev_device"
+
+
+Summary of changes from v126 to v127
+============================================
+
+Karel Zak (2):
+      build-sys: don't duplicate file names
+      build-sys: remove non-POSIX variable names
+
+Kay Sievers (26):
+      add inotify dummy definitions if inotify is not available
+      build: remove autopoint check
+      udevadm: trigger - add missing attr filter to synthesized "subsystem" register events
+      ignore duplicated rules file names
+      fix .gitignore
+      rules: delete all distro rules which do not use default rules
+      rules: add nvram
+      rules: add isdn rules
+      rules: Gentoo update
+      add missing includes
+      add some warnings
+      update .gitignore
+      add missing 'v' for "make changelog"
+      build: fix "make dist"
+      vol_id: make the --offset= argument optional
+      rules: optical drives - probe at last session offset, do not probe for raid
+      libudev: add library to access udev information
+      libudev: split source files
+      update INSTALL
+      libudev: add udev event monitor API
+      volume_id: remove deprecated functions and bump major version
+      volume_id: remove left-over fd close()
+      split udev_device.c to leave out rules handling from libudev
+      libudev: link against selinux if needed
+      firmware.sh: lookup lookup kernel provided firmware directory
+      libudev: require LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
+
+Michal Soltys (1):
+      rules: fix md rules for partitioned devices
+
+
+Summary of changes from v125 to v126
+============================================
+
+Kay Sievers (9):
+      delete all Makefiles and move udev source to udev/
+      use autotools
+      rules: mode 0660 for group "disk"
+      rules: update Fedora rules
+      update ChangeLog
+      INSTALL: --enable-selinux not --with-selinux
+      volume_id: move static lib to $prefix
+      volume_id: create relative links
+      rules: run vol_id on opticals only if media is found
+
+Marco d'Itri (1):
+      rules: Debian update
+
+Thomas Koeller (1):
+      use proper directory lib/lib64 for libvolume_id
+
+
+Summary of changes from v124 to v125
+============================================
+
+John Huttley (1):
+      rules: tape rules - add nst to usb and 1394 links
+
+Karl O. Pinc (1):
+      man: clarify $attr{} parent searching
+
+Kay Sievers (14):
+      collect: fix size_t printf
+      path_id: suppress trailing '-' like 'ID_PATH=pci-0000:05:01.0-'
+      rules: add v4l persistent links
+      docs: update some docs and delete outdated stuff
+      scsi_id: fix fallback to sg v3 for sg nodes
+      rules: fix cciss rules for partition numbers > 9
+      udev.conf: udevcontrol -> udevadm control
+      rules: use consistently OPTIONS+=
+      scsi_id: the fallback fix broke error handling
+      man: rebuild from xml
+      do not touch node ownership and permissions, if already correct
+      rules: tape rules - add nst to by-path/ links
+      udevadm: info - add --export format to --device-id-of-file=
+      move default rules from /etc/udev/rules.d/ to /lib/udev/rules.d/
+
+Marco d'Itri (7):
+      rules_generator: net rules - do not print error if file is missing and ignore commented rules
+      man: add link_priority default value
+      scsi_id: man page fix
+      udevadm: settle - add verbose output when running into timeout
+      rules: Debian update
+      rules: Debian update
+      ignore rule with GOTO to a non-existent label
+
+Thomas Koeller (1):
+      scsi_id: include sys/stat.h
+
+Tobias Klauser (1):
+      collect: check realloc return value
+
+
+Summary of changes from v123 to v124
+============================================
+
+Kay Sievers (1):
+      cdrom_id: fix recognition of blank media
+
+
+Summary of changes from v122 to v123
+============================================
+
+Erik van Konijnenburg (3):
+      add substitution in MODE= field
+      Makefile: use udevdir in "make install"
+      volume_id: support for oracleasm
+
+Harald Hoyer (1):
+      scsi_id: retry open() on -EBUSY
+
+Karel Zak (2):
+      volume_id: remove unnecessary global variable
+      volume_id: enable GFS probing code, add LABEL support
+
+Kay Sievers (5):
+      edd_id: call it only for sd* and hd*
+      rename WAIT_FOR_SYSFS to WAIT_FOR and accept an absolute path
+      rules: tape rules - use bsg device nodes for SG_IO
+      rules: persistent net - handle "locally administered" ibmveth MAC addresses
+      cdrom_id: export ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=, ID_CDROM_MEDIA_TRACK_COUNT_DATA=
+
+Michal Soltys (1):
+      man: add NAME== match entry
+
+Xinwei Hu (2):
+      collect: realloc buffer, if needed
+      udevd: export .udev/queue/$seqnum before .udev/uevent_seqnum
+
+
+Summary of changes from v121 to v122
+============================================
+
+Hannes Reinecke (2):
+      scsi_id: remove all sysfs dependencies
+      scsi_id: add SGv4 support
+
+Karel Zak (1):
+      volume_id: clean up linux_raid code
+
+Kay Sievers (8):
+      scsi_id:  update man page
+      scsi_id: remove bus_id option
+      scsi_id: add --sg-version= option
+      rules: adapt to new scsi_id
+      rules: adapt tape rules to new scsi_id
+      scsi_id: add bsg.h
+      volume_id: bump version
+      Makefile: do not create udevcontrol, udevtrigger symlinks
+
+MUNEDA Takahiro (2):
+      man: udevd- fix udev(8) reference
+      man: scsi_id
+
+Matthias Schwarzott (1):
+      cdrom_id: fix segfault
+
+
+Summary of changes from v120 to v121
+============================================
+
+Damjan Georgievski (1):
+      libvolume_id: recognize swap partitions with a tuxonice hibernate image
+
+Daniel Drake (1):
+      writing udev rules: fix rule typos
+
+David Woodhouse (1):
+      rules_generator: net rules - add "dev_id" value to generated rules
+
+Harald Hoyer (1):
+      selinux: more context settings
+
+Kay Sievers (21):
+      udevinfo: do not replace chars when printing ATTR== matches
+      vol_id: add --offset option
+      cdrom_id: replace with version which also exports media properties
+      udevd: at startup write message including version number to kernel log
+      rules_generator: net rules - always add KERNEL== match to generated rules
+      selinux: fix missing includes
+      allow setting of MODE="0000"
+      path_id: remove subsystem whitelist
+      logging: add trailing newline to all strings
+      scsi_id: initialize serial strings
+      persistent device naming: also read unpartitioned media
+      cdrom_id: add more help text
+      add $links substitution
+      fstab_import: add program to IMPORT matching fstab entry
+      add OPTIONS+="event_timeout=<seconds>"
+      write "event_timeout" to db
+      udevadm: trigger - add --env= option
+      udevadm: control - fix --env key to accept --env=<KEY>=<value>
+      udevadm: info - do not print ATTR{dev}==
+      persistent device naming: update tape rules
+      rules: update md rules
+
+
+Summary of changes from v119 to v120
+============================================
+
+Kay Sievers (9):
+      test: remove duplicated EXTRA entry
+      rules: remove last WAIT_FOR_SYSFS, load ppdev, switch scsi_device
+      udevadm: trigger - option to synthesize events and pass them to a socket
+      udevadm: info - resolve devpath if symlink is given
+      udevadm: remove old man page links and compat links for debugging tools
+      udevadm: trigger - fix broken socket option check
+      udevadm: trigger - fix --socket== + --verbose
+      also accept real socket files for RUN+="socket:<path>"
+      persistent device naming: cleanup storage rules
+
+Michael Kralka (1):
+      udevd: serialize events if they refer to the same major:minor number
+
+
+Summary of changes from v118 to v119
+============================================
+
+Anthony L. Awtrey (1):
+      do not skip RUN execution if device node removal fails
+
+Harald Hoyer (2):
+      rules: Fedora update
+      rules: do not set GROUP="disk" for scanners
+
+Jiri Slaby (1):
+      rules_generator: add missing write_net_rules unlock
+
+Karel Zak (2):
+      volume_id: fix UUID raw buffer usage
+      volume_id: fix typo in function documentation
+
+Kay Sievers (10):
+      switch mailing lists to linux-hotplug@vger.kernel.org
+      rules: remove tty rule which can never run because of an earlier "last_rule"
+      volume_id: update ext detection
+      selinux: set context for real file name not the temp name
+      hack to allow ATTR{block/*/uevent}="change"
+      rules_generator: add KERNEL=="<netifname>*" to generated rules
+      persistent device naming: also run on "change" event
+      test: add "subsystem" links to all devices
+      sysfs: depend on "subsystem" link
+      extend hack to allow TEST=="*/start"
+
+Matthias Schwarzott (1):
+      volume_id: respect LDFLAGS
+
+Neil Williams (1):
+      volume_id: add prefix=, exec_prefix=
+
+Roy Marples (1):
+      Makefile: do not require GNU install
+
+
+Summary of changes from v117 to v118
+============================================
+
+Daniel Drake (1):
+      doc: update "writing udev rules"
+
+Hannes Reinecke (1):
+      volume_id: LVM - add uuid
+
+Kay Sievers (9):
+      remove udevstart
+      rules_generator: do not create rules with insufficient matches
+      man: udevadm settle - mention 180 seconds default timeout
+      libvolume_id: squashfs - add endianess support for LZMA compression
+      rules: add AOE rule
+      volume_id: md - add metadata minor version
+      volume_id: run only once into a timeout for unreadable devices
+      create_floppy_devices: fix logic for more than one floppy device
+      volume_id: also add readable check to probe_all()
+
+Matthias Schwarzott (1):
+      rules: Gentoo update
+
+Michael Prokop (1):
+      libvolume_id: squashfs+LZMA compression detection
+
+
+Summary of changes from v116 to v117
+============================================
+
+Dan Nicholson (2):
+      extras: ignore built and generated files
+      volume_id: create relative symlink when $(libdir) = $(usrlibdir)
+
+Kay Sievers (15):
+      usb_id: fail if vendor/product can not be retrieved
+      rules: SUSE update
+      firmware: do not print error if logger is missing
+      volume_id: vfat - allow all possible sector sizes
+      volume_id: LUKS - export version
+      volume_id: ntfs - rely on valid master file table
+      volume_id: bump version
+      udevinfo: exclude "uevent" file from --attribute-walk
+      udevadm: merge all udev tools into a single binary
+      udevadm: accept command as option, like --help, --version
+      udevadm: add info option --device-id-of-file=<file>
+      Makefile: fix bogus version number than got committed
+      udevadm: also return major==0 results for --device-id-of-file
+      man: udevd.8 - remove udevcontrol section
+      udevadm: control - allow command to be passed as option
+
+MUNEDA Takahiro (1):
+      man: fix udevadm.8 typo
+
+Matthias Schwarzott (2):
+      firmware: remove hardcoded path to logger
+      rules: Gentoo update
+
+VMiklos (1):
+      rules: Frugalware update
+
+
+Summary of changes from v115 to v116
+============================================
+
+Bryan Kadzban (1):
+      rules: fix typos
+
+Harald Hoyer (3):
+      check line length after comment check and whitespace strip
+      only install *.rules
+      remove extra space from udevinfo symlink output
+
+Kay Sievers (29):
+      rules: fix two trivial typos
+      rules: random and urandom are 0666
+      rules: add REMOVE_CMD rule
+      track "move" events to rename database and failed files
+      rules: Gentoo update
+      rules: add i2o driver rule
+      man: recreate man pages
+      volume_id: fix linux_raid metadata version 1.0 detection
+      add $name substitution
+      do not delete the device node with ignore_remove, but handle the event
+      print warning for invalid TEST operations
+      rules: do not delete /lib/udev/devices/ nodes on "remove"
+      rules: remove broken nvram group assignment without any permission
+      add /dev/rtc symlink if new rtc drivers are used
+      increase WAIT_FOR_SYSFS timeout to 10 seconds
+      rules: put bsd nodes in /dev/bsd/ directory
+      path_id: fix for stacked class devices
+      ignore device node names while restoring symlinks from the stack
+      use SEQNUM in /dev/.udev/queue/ instead of devpath
+      rules: add memstick module loading
+      udevinfo: simplify symlink printing logic
+      prevent wrong symlink creation if database disagress with current rules
+      fix wrong variable used in logged string
+      update README
+      rule_generator: move all policy from write_net_rules to the rules file
+      rules: call usb_id only for SUBSYSTEMS=="usb"
+      rules: split out and fix persistent tape rules
+      fix debug output string
+      rule_generator: always match netif type in generated rule
+
+Matthias Schwarzott (3):
+      rules: Gentoo update
+      rules: Gentoo update
+      rules: Gentoo update
+
+Michael Morony (1):
+      set buffer size if strlcpy/strlcat indicate truncation
+
+maximilian attems (1):
+      correct includes in udev_selinux.c
+
+
+Summary of changes from v114 to v115
+============================================
+
+Harald Hoyer (1):
+      rules: fix typo in 80-drivers.rules
+
+Kay Sievers (15):
+      rules: add default rules
+      rules: update SUSE rules
+      rules: add packages rules
+      rules: add ia64 rules
+      rules: move md-raid rules to packages dir
+      rules: run vol_id only for partitions
+      rules: update Fedora rules
+      edd_id: move persistent rules to its own file
+      accept relative path for TEST
+      rules: add iowarrior rule
+      volume_id: fix sqashfs detection
+      do not ignore dynamic rule if it is the last one in the list
+      rule_generator: fix wrong DRIVERS!= logic
+      rules: update Fedora
+      Makefile: install default rules
+
+Marco d'Itri (3):
+      rules_generator: remove policy from write_cd_rules
+      rules_generator: fix write_cd_rules when similar names exist in the root directory
+      rules: Debian update
+
+
+Summary of changes from v113 to v114
+============================================
+
+Hannes Reinecke (3):
+      collect: extra to synchronize actions across events
+      add $driver subtitution
+      rules_generator: add S/390 persistent network support
+
+Kay Sievers (24):
+      rules_generator: remove executable flag from include file
+      always unlink temporary file before creating new one
+      rules: SUSE update
+      volume_id: ext4 detection
+      udevtrigger: allow to specify action string
+      add option to RUN key to ignore the return value of the program
+      use global udev_log variable instead of parameter in run_program
+      add udev_rules_run() to handle RUN list
+      move udev_utils_run.c into udev_rules.c
+      rules: SUSE update
+      name_list: rename loop_name -> name_loop
+      handle dynamic rules created in /dev/.udev/rules.d/
+      allow SYMLINK== match
+      libvolume_id: use /usr/$libdir in pc file
+      Makefile: add --as-needed flag to ld
+      restore behavior of NAME==
+      rules_generator: remove "installation" function
+      udevtrigger: trigger "driver" events
+      rules: update SUSE
+      rules: Fedora update
+      rules: add "do not edit" comment
+      rules: Fedora update
+      rules_generator: skip random MAC addresses
+      write changed network interface names to the kernel log
+
+Matthias Schwarzott (3):
+      rules: Gentoo update
+      fix inotify to work not only once
+      rules: Gentoo update
+
+Richard Hughes (1):
+      Makefile: add "make dist" for nightly snapshots
+
+
+Summary of changes from v112 to v113
+============================================
+
+David Zeuthen (1):
+      vol_id: do not fail if unable to drop privileges
+
+Kay Sievers (12):
+      add missing ChangeLog
+      make ATTR{[$SUBSYSTEM/$KERNEL]<attr>}="<value>" working
+      rules: recognize partitions and disk devices properly
+      rules: SUSE update
+      atomically replace existing nodes and symlinks
+      do not try to create existing file
+      info() for ignore_remove
+      rules: SUSE update
+      Makefile: check for missing ChangeLog or RELEASE-NOTES at release
+      allow to disable the replacement of unusual characters
+      no newline in log messages
+      udevd: do not use syslog if --verbose (debugging) is used
+
+Tobias Klauser (1):
+      fix typo in udev_utils_run.c
+
+
+Summary of changes from v111 to v112
+============================================
+
+Fabio Massimo Di Nitto (1):
+      rules: ignore partitons that span the entire disk
+
+Hannes Reinecke (1):
+      cciss device support
+
+Kay Sievers (34):
+      udevd: close /proc/meminfo after reading
+      create_floppy_devices: remove dead "unlink" code
+      volume_id: add function documentation
+      udev_db: escape path names with \x00 instead of %00
+      udevsettle: use long options
+      replace_chars: replace spaces in node name
+      volume_id: add and export string encoding function
+      vol_id: export encoded strings
+      rules: use encoded strings instead of skipping characters
+      udevtest: print message before log output
+      volume_id: escape % character
+      replace_chars: replace % character
+      IMPORT: do not mangle whitespace
+      scsi_id: do not install symlink in /sbin
+      rules: SUSE update
+      volume_id: terminate overlong label strings
+      scsi_id: add long options
+      rules: use long options for scsi_id
+      path_id: skip subsystem directory
+      rules: fix cciss rule
+      rules: SUSE update
+      scsi_id: fix typo in help text
+      fix "do not access parent" warning for ATTR{}
+      sysfs: add device lookup by $SUBSYSYTEM:$KERNEL
+      events for "bus" and "class" registration must be matched as "subsystem"
+      udevtest: add --subsystem option
+      sysfs: change order of subsystem lookup
+      add $sys substitution
+      add TEST=="<file>" key
+      add "[$SUBSYSTEM/$KERNEL]<attribute>" lookup
+      sysfs: handle bus/class top-level directories
+      sysfs: skip unknown sysfs directories
+      rules: SUSE update
+      release 112
+
+Miklos Vajna (2):
+      create_floppy_devices: add man page
+      path_id: remove on make uninstall
+
+Ryan Lortie (1):
+      volume_id: support for long-filename based labels
+
+Scott James Remnant (2):
+      replace_untrusted_chars: replace all whitespace with space
+      run_program: log "info" not "error" if program is missing
+
+
+Summary of changes from v110 to v111
+============================================
+
+Kay Sievers (19):
+      rules: SUSE update
+      rules: Fedora update
+      volume_id: use md native uuid format
+      vol_id: use long options
+      volume_id: add volume_id_get_* functions
+      vol_id: use volume_id_get_*
+      udevd: use fgets() to read /proc files
+      volume_id: add internal UUID_STRING
+      volume_id: add DDF support
+      vol_id: README update
+      volume_id: rename UUID_64BIT_LE/BE
+      vol_id: add ID_FS_UUID_SAFE
+      rules: use ID_FS_UUID_SAFE
+      rules: SUSE update
+      volume_id: give access to list of all available probers
+      vol_id: use libvolume_id prober list for --probe-all
+      volume_id: add remaining names for prober lookup by type
+      rules: SUSE update
+      volume_id: vol_id depends on libvolume_id
+
+Matthias Schwarzott (2):
+      volume_id: fix Makefile for parallel make
+      rules: Gentoo update
+
+
+Summary of changes from v109 to v110
+============================================
+
+Harald Hoyer (1):
+      udevcontrol: allow to set global variables in udevd
+
+Kay Sievers (13):
+      remove eventrecorder.sh
+      update SUSE rules
+      volume_id: add md metadata 1.0, 1.1, 1.2 support
+      unset variable with ENV{VAR}=""
+      delete copies of default rules in SUSE rules
+      volume_id: ext - fix endianess in version number
+      rules: Fedora update
+      volume_id: old md metadata has only 32 bit for the uuid
+      volume_id: minix version 3 support
+      don't create $tempnode for devices without major
+      usb_id: add <devpath> to help text
+      ata_id: use getopt_long()
+      rules: SUSE update
+
+Matthias Schwarzott (3):
+      Makefile: respect CFLAGS/LDFLAGS
+      rules: Gentoo update
+      ata_id: don't log error for libata devices on older kernels
+
+
+Summary of changes from v108 to v109
+============================================
+
+Harald Hoyer (1):
+      create_floppy_devices: create nodes with correct selinux context
+
+Kay Sievers (11):
+      udevtest: export ACTION string if given as option
+      update SUSE rules
+      make ACTION!="add|change" working
+      udevtest: import uevent variables if possible
+      udevinfo: export all information stored in database
+      default rules: add libata compat links
+      create_path: don't fail if something else created the directory
+      udevd: fix serialization of events
+      path_id: remove broken example
+      libvolume_id: do not install static library
+      update SUSE rules
+
+Matthias Schwarzott (2):
+      update Gentoo rules
+      persistent device naming: add joystick links
+
+VMiklos (1):
+      path_id: add man page
+
+
+Summary of changes from v107 to v108
+============================================
+
+Kay Sievers (3):
+      udevinfo: relax check for the correct device if looked up by name
+      don't write to sysfs files during test run
+      finally remove the directory event-multiplexer crap
+
+Matthias Schwarzott (2):
+      write_cd_rules: set default link type to "by-id" for usb and ieee1394 devices
+      update Gentoo rules
+
+Pozsar Balazs (1):
+      udevsettle: read udev not kernel seqnum first
+
+
+Summary of changes from v106 to v107
+============================================
+
+Jean Tourrilhes (1):
+      udevtest: export UDEV_LOG if we changed it
+
+Kay Sievers (33):
+      man: add missing options to various man pages
+      man: fix typo
+      create_floppy_devices: apply specified mode without umask
+      man: spelling fixes
+      udevmonitor: add switch for kernel and udev events
+      default rules: wait for 0:0:0:0 scsi devices only
+      update Fedora rules
+      delete dasd_id, it moved to s390-tools
+      update Gentoo rules
+      encode db-file names, instead of just replacing '/'
+      update internal variables if we see $DEVPATH during IMPORT
+      increase /proc/stat buffer
+      maintain index over device-names to devpath relation
+      restore overwritten symlinks when the device goes away
+      store devpath with the usual leading slash
+      add link_priority to rule options, and store it in database
+      pick actual valid device in udev_db_lookup_name
+      cleanup already existing db-entries and db-index on device update
+      selinux: move selinux_exit() to the main programs
+      remove old error message
+      read list of devices from index, make index private to database
+      priority based symlink handling
+      volume_id: get rid of compiler warning
+      udevinfo: remove -d option
+      update %n on netif name change
+      if a node goes away, possibly restore a waiting symlink
+      update TODO
+      man: add "link_priority" option
+      update SUSE rules
+      udevtest: add --force mode
+      udevinfo: print link priority
+      usb_id: append target:lun to storage device serial
+      run_directory: add final warning before removal
+
+Marco d'Itri (1):
+      update Debian rules
+
+Matthias Schwarzott (2):
+      udevd: cleanup std{in,our,err} on startup
+      udevmonitor: fix swapped event switch descriptions
+
+
+Summary of changes from v105 to v106
+============================================
+
+A. Costa (1):
+      man: fix typos in scsi_id and udevd
+
+Andrey Borzenkov (2):
+      vol_id: add -L to print raw partition label
+      vol_id: document -L
+
+Jamie Wellnitz (1):
+      persistent device naming: tape devices and medium changers
+
+Kay Sievers (15):
+      exclude parent devices from DRIVER== match
+      volume_id: really fix endianess bug in linux_raid detection
+      release 105
+      man: correct udevinfo --export-db
+      path_id: append LUN to iSCSI path
+      create_floppy_devices: add option for owner/group
+      update example rules
+      apply format chars to ATTR before writing to sysfs
+      add (subsystem) to udevmonitor output
+      update DRIVER== changes
+      remove --version from the udevinfo man page
+      add test for an attribute which contains an operator char
+      man: add note about parent matching behavior
+      scsi_id: accept tabs in /etc/scsi_id.conf
+      remove dead rule in persistent tape rules
+
+Matthias Schwarzott (4):
+      correct typo in extras/scsi_id/scsi_id.conf
+      fix retry-loop in netif-rename code
+      add option --version to udevd
+      rule_generator: fix for creating rules on read-only filesystem
+
+Peter Breitenlohner (1):
+      fix INSTALL_PROGRAM vs. INSTALL_SCRIPT
+
+Sergey Vlasov (3):
+      udevd: init signal pipe before daemonizing
+      unlink old database file before creating a new one
+      fix %c $string substitution
+
+Theodoros V. Kalamatianos (1):
+      fix udev attribute names with a colon
+
+
+Summary of changes from v104 to v105
+============================================
+
+A. Costa (1):
+      man: fix typos in scsi_id and udevd
+
+Andrey Borzenkov (2):
+      vol_id: add -L to print raw partition label
+      vol_id: document -L
+
+Kay Sievers (2):
+      exclude parent devices from DRIVER== match
+      volume_id: really fix endianess bug in linux_raid detection
+
+Matthias Schwarzott (2):
+      correct typo in extras/scsi_id/scsi_id.conf
+      fix retry-loop in netif-rename code
+
+Peter Breitenlohner (1):
+      fix INSTALL_PROGRAM vs. INSTALL_SCRIPT
+
+Sergey Vlasov (3):
+      udevd: init signal pipe before daemonizing
+      unlink old database file before creating a new one
+      fix %c $string substitution
+
+
+Summary of changes from v103 to v104
+============================================
+
+Kay Sievers (12):
+      update Fedora rules
+      update example rules
+      update SUSE rules
+      update SUSE rules
+      volume_id: fix endianess bug in linux_raid detection
+      man: fix udevmonitor text
+      man: recreate from xml
+      rename config "filename" to "dir"
+      remove outdated documentation
+      rename "udev.c" to "test-udev.c" - it is only for testing
+      update Fedora rules
+      use git-archive instead of git-tar-tree
+
+Kazuhiro Inaoka (1):
+      inotify syscall definitions for M32R
+
+Marco d'Itri (2):
+      write_cd_rules: identity-based persistence
+      scsi_id: remove trailing garbage from ID_SERIAL_SHORT
+
+Russell Coker (1):
+      SELinux: label created symlink instead of node
+
+
+Summary of changes from v102 to v103
+============================================
+
+Kay Sievers:
+      persistent storage rules: skip gnbd devices
+      volume_id: add checksum check to via_raid
+      volume_id: add comment about hfs uuid conversion
+      update SUSE rules
+      update Fedora rules
+
+
+Summary of changes from v101 to v102
+============================================
+
+Daniel Drake:
+      writing_udev_rules: fix typo in example rule
+
+Kay Sievers:
+      create missing ChangeLog for version 101
+      update SUSE rules
+      update default rules
+      first try "subsystem" link at a parent device, before guessing
+      if /sys/subsystem exists, skip class, bus, block scanning
+      scsi_id: export ID_SERIAL_SHORT without vendor/product
+      update SUSE rules
+
+MUNEDA Takahiro:
+      path_id: fix SAS disk handling
+
+
+Summary of changes from v100 to v101
+============================================
+
+Arjan Opmeer:
+      fix udevinfo help text typo
+
+Bryan Kadzban:
+      cleanup default rules
+      add IMPORT operations to the udev man page
+
+Kay Sievers:
+      remove Makefile magic for leading '0' in version
+      udevd: use getopt_long()
+      udevd: add --verbose option to log also to stdout
+      udevd: add --debug-trace option
+      rule_generator: improve net rule comment generation
+      volume_id: correct iso9660 high sierra header
+      warn if a PHYSEDV* key, the "device" link, or a parent attribute is used
+      don't print PHYSDEV* warnings for old WAIT_FOR_SYSFS rules
+      udevinfo: print error in --attribute-walk
+      udev_sysfs: unify symlink resolving
+      udevtrigger: trigger devices sorted by their dependency
+      fix spelling in deprecation warning
+      release 101
+
+Michał Bartoszkiewicz:
+      udevtrigger: fix typo that prevents partition events
+
+Miles Lane:
+      clarify "specified user/group unknown" error
+
+Piter PUNK:
+      update slackware rules
+
+VMiklos:
+      update Frugalware rules
+
+
+Summary of changes from v099 to v100
+============================================
+
+Kay Sievers:
+      update SUSE rules
+      fix messed up ChangeLog from release 099
+      man: add $attr{} section about symlinks
+      revert persistent-storage ata-serial '_' '-' replacement
+
+
+Summary of changes from v098 to v099
+============================================
+
+Greg KH:
+      update Gentoo rules
+
+Kay Sievers:
+      udev_db.c: include <sys/stat.h>
+      use fnmatch() instead of our own pattern match code
+      rename major/minor variable to maj/min to avoid warning
+      update source file headers
+      udevtest: print header that ENV{} can't work
+      update TODO
+      udevtrigger: options to filter by subsystem and sysfs attribute
+      udevtrigger: remove unused longindex
+      udevinfo: use long options
+      udevd: use files instead of symlinks for /dev/.udev/queue,failed
+      udevtrigger: fix pattern match
+      reorder options in udevinfo man page
+      udevinfo: fix SUBSYTEMS spelling error
+      fix ENV{TEST}="Test: $env{TEST}"
+      let $attr{symlink} return the last element of the path
+      cdrom_id: add rules file to call cdrom_id
+      udevinfo: do not show symlinks as attributes in --attribute-walk
+      remove broken name_cdrom.pl
+
+Marco d'Itri:
+      update Debian rules
+      run_program: close pipe fd's which are connected to child process
+      add persistent rules generator for net devices and optical drives
+
+MUNEDA Takahiro:
+      changes rules for ata disk from '_' to '-'
+
+Sergey Vlasov:
+      make struct option arrays static const
+      fix "subsytem" typo
+
+
+Summary of changes from v097 to v098
+============================================
+
+Alex Merry:
+      udevtest: allow /sys in the devpath paramter
+
+Harald Hoyer:
+      selinux: init once in the daemon, not in every event process
+
+Kay Sievers:
+      udevd: remove huge socket buffer on the control socket
+      man page: fix typo
+      rename udev_libc_wrapper -> udev_sysdeps
+      db: store devpath - node relationship for all devices
+      udevinfo: allow -a -n <node>
+      udevinfo, udevtest: simplify '/sys' stripping from devpath argument
+      lookup_user, lookup_group: report "unknown user" and "lookup failed"
+      consistent key naming to match only the event device or include all parent devices
+      skip rule, if too may keys of the same type are used
+      introduce ATTR{file}="value" to set sysfs attributes
+      update SUSE rules
+      update default rules
+      export DRIVER for older kernels as a replacement for PHYSDEVDRIVER
+      fix typo in SUBSYSTEMS key parsing
+      udevtrigger: add --retry-failed
+      volume_id: add suspend partition detection
+      vol_id: use primary group of 'nobody' instead of 'nogroup'
+      remove built-in /etc/passwd /etc/group parser
+      always expect KEY{value} on ATTR, ATTRS, ENV keys
+      use new key names in test programs
+      cleanup commandline argument handling
+      db: don't create a db file for only a node name to store
+      man: add ATTR{file}="value" assignment
+
+Lennart Poettering:
+      volume_id: fix fat32 cluster chain traversal
+
+Marco d'Itri:
+      fix 'unknow user' error from getpwnam/getgrnam
+      fix rc when using udev --daemon
+      update Debian rules
+
+Michał Bartoszkiewicz:
+      man pages: fix typos
+
+
+Summary of changes from v096 to v097
+============================================
+
+Anssi Hannula:
+      add joystick support to persistent input rules
+
+Kay Sievers:
+      firmware.sh: remove needless '/'
+      vol_id: add --skip-raid and --probe-all option
+      switch uevent netlink socket to group 1 only
+      increase /proc/stat read buffer
+      use "change" instead of "online" events
+      remove 'static' from local variable
+      libvolume_id: add parameter 'size' to all probe functions
+      man pages: replace 'device-path' by 'devpath'
+      man pages: work around xmlto which tries to be smart
+      refresh vol_id man page
+      udevinfo: add DRIVER==
+      Makefile: fix dependency
+      libvolume_id: read ufs2 label
+      switch ifdef __KLIBC__ to ifndef __GLIBC__
+      report failing getpwnam/getgrnam as error
+      rename udevcontrol message types and variables
+      initialize unused sockets to -1
+      udevd: remove useless udevinitsend parameter
+      update README
+      udevd: autotune max_childs/max_childs_running
+      update frugalware rules
+      update SUSE rules
+      move default rules to etc/udev/rules.d/
+      add 'crypto' devices to persistent storage rules
+      add late.rules to default rules
+      update Fedora rules
+      don't report an error on overlong comment lines
+      update SUSE rules
+      udevd: read DRIVER from the environment
+
+Marco d'Itri:
+      make rename_netif() error messages useful
+      path_id: fix an harmless syntax error
+
+Piter PUNK:
+      update slackware rules
+
+Richard Purdie:
+      Fix inotify syscalls on ARM
+
+
+Summary of changes from v095 to v096
+============================================
+
+Kay Sievers:
+      Makefiles: fix .PHONY for man page target
+      allow longer devpath values
+      path_id: prepare for new sysfs layout
+
+
+Summary of changes from v094 to v095
+============================================
+
+Kay Sievers:
+      update SUSE rules
+      don't remove symlinks if they are already there
+      allow "online" events to create/update symlinks
+      udevinfo: clarify parent device attribute use
+      update SUSE rules
+      netif rename: optimistic loop for the name to become free
+      remove broken %e enumeration
+
+Tobias Klauser:
+      print usage of udevcontrol when no or invalid command is given
+
+
+Summary of changes from v093 to v094
+============================================
+
+Daniel Drake:
+      update "writing udev rules"
+
+Kay Sievers:
+      libvolume_id: gfs + gfs2 support
+      remove MODALIAS key and substitution
+      add persistent-input.rules
+
+Marco d'Itri:
+      update Debian rules
+
+
+Summary of changes from v092 to v093
+============================================
+
+Hannes Reinecke:
+      path_id: add support for iSCSI devices
+
+Kay Sievers:
+      libvolume_id: fat - check for signature at end of sector
+      libvolume_id: add more software raid signatures
+      update Fedora rules
+      path_id: prevent endless loop for SAS devices on older kernels
+      remove udevsend
+      replace binary firmware helper with shell script
+      skip device mapper devices for persistent links
+
+
+Summary of changes from v091 to v092
+============================================
+
+Kay Sievers:
+      don't include stropts.h, some libc's don't like it
+      udevd: create leading directories for /dev/.udev/uevent_seqnum
+      vol_id: fix logging from libvolume_id's log function
+      update SUSE rules
+      update SUSE rules
+      add more warnings for invalid key operations
+      fix offsetof() build issue with recent glibc
+      selinux: fix typo in block device node selection
+      vol_id: add NetWare volume detection
+      edd_id: fix "(null)" output if "mbr_signature" does not exist
+      update Fedora rules
+      libvolume_id: nss - use different uuid
+
+Libor Klepac:
+      path_id: add platform and serio support
+
+Marco d'Itri:
+      update Debian rules
+      path_id: fix bashism
+
+
+Summary of changes from v090 to v091
+============================================
+
+Hannes Reinecke:
+      path_id: fix SAS device path generation
+
+Kay Sievers:
+      udevtest: don't try to delete symlinks
+      persistent rules: fix typo in dm rule
+      allow NAME=="value" to check for already assigned value
+      udevd: export initial sequence number on startup
+
+
+Summary of changes from v089 to v090
+============================================
+
+Kay Sievers:
+      udevd: export current seqnum and add udevsettle
+      volume_id: fix endianess conversion typo for FAT32
+      merge device event handling and make database content available on "remove"
+      set default udevsettle timeout to 3 minutes
+      export INTERFACE_OLD if we renamed a netif
+      let udevmonitor show the possibly renamed devpath
+      volume_id: move some debug to info level
+      udevtrigger: fix event order
+      usb_id: remove uneeded code
+      remove old symlinks before creating current ones
+      path_id: fix loop for SAS devices
+      apply format char to variables exported by ENV
+
+Marco d'Itri:
+      add inotify support for hppa and MIPS and log if inotify is not available
+
+Matt Kraai:
+      fix typo in error message
+
+
+Summary of changes from v088 to v089
+============================================
+
+Hannes Reinecke:
+      path_id: add bus to USB path
+
+Kay Sievers:
+      change rule to skip removable IDE devices
+      don't create uuid/label links for raid members
+      volume_id: provide library
+      fix rule order for persistent tape links
+      update man page
+      volume_id: provide a custom debug function
+      volume_id: rename subdirectory
+      volume_id: use shared library by default
+      because is better than cause
+      volume_id: remove some global symbols
+      volume_id: define exported symbols
+      remove all stripping code
+      man pages: mention udev(7) not udev(8)
+      update Debian rules
+      move all *_id programs to /lib/udev/
+      update Red Hat rules
+      update SUSE rules
+      pass CROSS_COMPILE to AR and RANLIB down to extras/
+      volume_id: update README
+      volume_id: generate man page from xml source
+      update README
+      fix symlink targets in Makefiles
+
+
+Summary of changes from v087 to v088
+============================================
+
+Hannes Reinecke:
+      persistent links: add scsi tape links and usb path support
+
+Kay Sievers:
+      volume_id: add squashfs detection
+      reset signal handler in event process
+      correct use of fcntl()
+      add udevtrigger to request events for coldplug
+      add ',' to trusted chars
+      volume_id: remove partition table parsing code
+      volume_id: remove all partition table support
+      fix spelling error in debug string
+      rename "persistent disk" to "persistent storage"
+      fix output for USB path
+
+
+Summary of changes from v086 to v087
+============================================
+
+Hannes Reinecke:
+      path_id: support SAS devices
+
+Kay Sievers:
+      fix persistent disk rules to exclude removable IDE drives
+      warn about %e, MODALIAS, $modalias
+      remove devfs rules and scripts
+
+Masatake YAMATO:
+      typo in debug text in udev_run_hotplugd.c
+
+
+Summary of changes from v085 to v086
+============================================
+
+Kay Sievers:
+      volume_id: replace __packed__ by PACKED macro
+      volume_id: split raid and filesystem detection
+      volume_id: add missing return
+      udevd: fix queue export for multiple events for the same device
+
+Kyle McMartin:
+      workaround missing kernel headers for some architectures
+
+Nix:
+      update to udev-084/doc/writing_udev_rules
+
+
+Summary of changes from v084 to v085
+============================================
+
+Andrey Borzenkov:
+      Fix trivial spelling errors in RELEASE-NOTES
+
+Jeroen Roovers:
+      fix typo in parisc support to path_id
+
+Kay Sievers:
+      make WAIT_FOR_SYSFS usable in non "wait-only" rules
+      fix typo in man page
+      include sys/socket.h for klibc build
+      cramfs detection for bigendian
+      exit WAIT_FOR_SYSFS if the whole device goes away
+      update SUSE rules
+      update Red Hat rules
+      update Gentoo rules
+      include errno.h in udev_libc_wrapper.c
+
+
+Summary of changes from v083 to v084
+============================================
+
+Kay Sievers:
+      update SUSE rules
+      switch CROSS to CROSS_COMPILE
+      replace fancy silent build program by simple kernel build like logic
+      move manpages to top level
+      remove UDEVD_UEVENT_INITSEND
+      whitespace fixes
+      scsi_id: remove dead files
+      optimize sysfs device and attribute cache
+      let SYSFS{} look at the device, not only the parent device
+      add debug output to sysfs operations
+
+
+Summary of changes from v082 to v083
+============================================
+
+Andrey Borzenkov:
+      man page: document when substitutions are applied for RUN and other keys
+      check for ignore_device in loop looks redundant
+
+Kay Sievers:
+      udevstart: fix NAME="" which prevents RUN from being executed
+      find programs in /lib/udev for IMPORT if {program} is not given
+      don't add $SUBSYSTEM automatically as $1 to programs
+      remove redundant substitution of RUN key
+
+
+Summary of changes from v081 to v082
+============================================
+
+Andrey Borzenkov:
+      substitute format chars in RUN after rule matching
+
+Kay Sievers:
+      scsi_id, usb_id: request device parent by subsystem
+      path_id: work with "all devices in /sys/devices"
+      ignore all messages with missing devpath or action
+      Makefile: remove dynamic config file generation
+      path_id: handle fiber channel (Hannes Reinecke <hare@suse.de>)
+      usb_id: don't fail on other subsytems than "scsi"
+      don't do RUN if "ignore_device" is given
+      increase kernel uevent buffer size
+      move udev(8) manpage to udev(7)
+      recreate man pages from xml source
+      remove udev, udevstart, udevsend from the default installation
+      update SUSE rules
+      rename apply_format() cause it is public now
+      udevtest: add udev_rules_apply_format() to RUN keys
+      let "ignore_device" always return the event successfully
+
+Olivier Blin:
+      fixes udev build with -fpie
+
+
+Summary of changes from v080 to v081
+============================================
+
+Kay Sievers:
+      add DEVLINKS to "remove" event
+      better log text and comments
+      vol_id: probe volume as user nobody
+      fix BUS, ID, $id usage
+      prepare moving of /sys/class devices to /sys/devices
+
+
+Summary of changes from v079 to v080
+============================================
+
+Brent Cook:
+      fix dependency for make -j2
+
+coly:
+      fix man page typos
+
+Kay Sievers:
+      update RELEASE-NOTES + TODO
+      fix typo in man page
+      update TODO
+      update SUSE rules
+      path_id: fix invalid character class
+      replace libsysfs
+
+Marco d'Itri:
+      udev_selinux.c: include udev.h
+
+
+Summary of changes from v078 to v079
+============================================
+
+Kay Sievers:
+      don't log error if database does not exist
+      use udev_root instead of "/dev"in selinux matchpathcon_init_prefix()
+      scsi_id: read page 0x80 with libata drives
+      update SUSE rules
+      remove %e from man page
+
+
+Summary of changes from v077 to v078
+============================================
+
+Greg Kroah-Hartman:
+      Update Gentoo udev main rule file.
+      add parisc support to path_id
+
+Hannes Reinecke:
+      scsi_id: -u fold multiple consecutive whitespace chars into single '_'
+
+Harald Hoyer:
+      optimize SELinux path match
+
+Kay Sievers:
+      update README
+      allow C99 statements
+      fix segfaulting create_floppy_devices
+      update SUSE rules
+      remove unused variables
+      remove default settings in udev.conf
+      clearenv() is now part of klibc
+      add DEVLINKS to the event environment
+
+Kurt Garloff:
+      scsi_id: support pre-SPC3 page 83 format
+
+
+Summary of changes from v076 to v077
+============================================
+
+Kay Sievers:
+      merge two consecutive static strlcat's
+      don't return an error, if "ignore_device" is used
+      remove outdated and misleading stuff
+      move SEQNUM event skipping to udevsend
+      update RELEASE-NOTES
+      update SUSE rules
+      allow programs in /lib/udev called without the path
+      update SUSE rules
+      add target to to generate ChangeLog section
+      update Red Hat rules
+
+Marco d'Itri:
+      allow to overwrite the configured udev_root by exporting UDEV_ROOT
+      let udevsend ignore events with SEQNUM set
+      update Debian rules
+
+
+Summary of changes from v75 to v076
+============================================
+
+Kay Sievers:
+      fix typo in eventrecorder
+      volume_id: include stddef.h header
+      remove misleading install instructions
+      remove all built-in wait_for_sysfs logic
+      add linux/types.h back, old glibc-kernel-headers want it
+      volume_id: use glibc's byteswap
+      udevd: ignore all messages without DEVPATH
+      udevd: track exit status of event process
+      udevd: export event queue and event state
+      remove "udev_db" option from config file
+      Makefile: remove exec_prefix and srcdir
+      update README and RELEASE-NOTES
+      udevd: track killed event processes as failed
+      update README
+      don't start udevd from udevsend
+      udevd: add a missing return
+      libvolume_id: fix weird fat volume recognition
+      move some helpers from extras to /lib/udev
+
+Scott James Remnant:
+      move delete_path() to utils
+      clean-up empty queue directories
+      Makefile: fail, if submake fails
+
+
+Summary of changes from v74 to v075
+============================================
+
+Greg Kroah-Hartman:
+      Make run_directory.c stat the place it is going to try to run.
+
+Kay Sievers:
+      forgot the ChangeLog for 074
+      volume_id: provide libvolume_id.a file
+      remove our own copy of klibc
+      remove outdated HOWTO
+      update TODO
+      update SUSE rules
+      remove completely useless start script
+      fix tests and remove no longer useful stuff
+      replace udeveventrecorder by a shell script
+
+
+Summary of changes from v73 to v074
+============================================
+
+Kay Sievers:
+      never queue events with TIMEOUT set
+      let NAME="" supress node creation, but do RUN keys
+      remove udevinitsend
+      update .gitignore
+
+Marco d'Itri:
+      add strerror() to error logs
+      move some logging from dbg() to info()
+
+
+Summary of changes from v72 to v073
+============================================
+
+Kay Sievers:
+      udevd: depend on netlink and remove all sequence reorder logic
+      print useconds in udevmonitor
+      add RELEASE-NOTES, update TODO
+
+
+Summary of changes from v71 to v072
+============================================
+
+Ananth N Mavinakayanahalli:
+  libsysfs: translate devpath of the symlinked class devices to its real path
+
+Jan Luebbe:
+  add man pages for *_id programs
+
+Kay Sievers:
+  volume_id: add OCFS Version 1
+  volume_id: add Veritas fs
+  volume_id: check ext fs for valid blocksize, cause magic is only 2 bytes
+  volume_id: move blocksize validation to fix jbd recognition
+  volume_id: fix typo in ocfs
+  volume_id: add vxfs include
+  volume_id: make FAT32 recognition more robust
+  volume_id: Version 051
+  volume_id: fix typo in ext blocksize check
+  volume_id: Version 052
+  FAQ: remove confusing statement about module loading
+  cleanup compiler/linker flags
+  use DESTDIR on uninstall, no need to pass prefix to submake
+  allow to pass STRIPCMD, to skip stripping of binaries
+  cleanup make release
+  fix the new warnings I asked for
+  move rules parsing into daemon
+  "make STRIPCMD=" will disable the stripping of binaries
+  remove no longer working udevd-test program
+  "STRIPCMD=" for the EXTRAS
+  add dummy inotify syscalls on unsupported architecture
+  remove no longer needed waiting for "dev" file
+  revert the "read symlink as device patch"
+  use libsysfs to translate the class linke to the device path
+  libsysfs: remove brute-force "bus", "driver" searching for old kernels
+  test: add "driver" and "bus" links to test sysfs tree
+  update RELEASE-NOTES
+  udevd: don't daemonize before initialization
+  log to console if syslog is not available
+  udevd: disable OOM
+  remove precompiled rules option
+  export DEVNAME on "remove" only if we really got a node to remove
+  fix typo in umask()
+
+
+Summary of changes from v70 to v071
+============================================
+
+Greg Kroah-Hartman:
+      Remove the udev.spec file as no one uses it anymore
+
+John Hull:
+      edd_id: check that EDD id is unique
+
+Kay Sievers:
+      ata_id: open volume O_NONBLOCK
+      add "Persistent Device Naming" rules file for disks
+      scsi_id: switch temporary node creation to /dev
+      volume_id: set reiser instead of reiserfs for filesystem type
+      update devfs rules header
+      update Debian rules
+      update Fedora rules
+      update Debian rules
+      remove no longer needed includes
+      switch tools and volume_id from LGPL to GPLv2
+      add edd-*-part%n to the persistent.rules
+      update Debian persistent rules
+      clarify README
+      udevd: fix initial timeout handling
+      force event socket buffer size to 16MB
+      udevd: move logging from err to info for non-hotplug uevent
+      fix selinux compilation
+      libsysfs: accept sysmlinks to directories instead of real directories
+
+Marco d'Itri:
+      run_directory: fix typo in "make install"
+
+
+Summary of changes from v069 to v070
+============================================
+
+Amir Shalem:
+  udevd: fix udevd read() calls to leave room for null byte
+
+Edward Goggin:
+  scsi_id: derive a UID for a SCSI-2 not compliant with the page 83
+
+Greg Kroah-Hartman:
+  fix nbd error messages with a gentoo rule hack
+  fix scsi_id rule in gentoo config file
+
+Jürg Billeter:
+  EXTRAS/Makefile: fix install targets to match main Makefile
+
+Kay Sievers:
+  volume_id: fix error handling with failing read()
+  EXTRAS: cleanup and sync all Makefiles
+  add install test to 'make buildtest'
+  update RELEASE-NOTES
+
+Olivier Blin:
+  fix a debug text typo in udev_rules.c
+
+
+Summary of changes from v068 to v069
+============================================
+
+Amir Shalem:
+  fix typo in firmware_helper
+
+Duncan Sands:
+  firmware_helper: fix write count
+
+Kay Sievers:
+  *_id: fix zero length in set_str()
+  add program name to logged error
+  fix exit code of udevinitsend and udevmonitor
+  udevd: keep the right order for messages without SEQNUM
+  volume_id: don't probe for mac_partition_maps
+  udevmonitor: cleanup on exit
+  path_id: remove SUSE specific PATH
+  update SUSE rules
+  add pci_express to bus list
+  update SUSE rules
+  store ENV{key}="value" exported keys in the database
+  fix lookup for name in the udevdb, it should return the devpath
+  prepare for new HAL udevdb dump
+  print persistent data with "udevinfo -q all"
+  change parameter order of udev_db_search_name()
+  add and use name_list_cleanup() for cleaning up the string lists
+  don't store devpath in udevdb, we don't need it
+  add uft8 validation for safe volume label exporting
+  start to enforce plain ascii or valid utf8
+  use WRITE_END/READ_END for the pipe index
+  remove not needed sig_flag for state of signal_pipe
+  don't reenter get_udevd_msg() if message is ignored
+  rename ...trailing_char() to ...trailing_chars()
+  vol_id: ID_LABEL_SAFE will no longer contain fancy characters
+  udevd: move some logging to "info" and "err"
+  remove special TIMEOUT handling from incoming queue
+  udev_test.pl: we replace untrusted chars with '_'
+  check the udevdb before assigning a new %e
+  update RELEASE-NOTES
+  udevinfo: add database export
+  write man page masters in DocBook XML
+  udevinfo: rename dump() to export()
+  test the automatic man page rebuild and checkin
+  Makefile: remove all the duplicated rules
+  all man pages rewritten to use DocBook XML
+  add missing udevsend man page
+  also forgot udevmonitor.8
+  udevinfo: restore -d option
+  scsi_id: rename SYSFS to LIBSYSFS
+  add edd_id tool to match BIOS EDD disk information
+  move and update libsysfs.txt
+  klibc: update to version 1.1.1
+  delete cdromsymlinks* - obsoleted by cdrom_id and IMPORT rules
+  delete docs/persistent_naming - obsoleted by persistent disk names
+  delete old Fedora html page
+  add "totally outdated" header to docs/overview :)
+  update SUSE rules
+  fix useless but funny name_cdrom.pl script to work again
+  update TODO
+  Makefile: fix prerequisits for $(PROGRAMS)
+  Makefile: cleanup install targets
+  remove chassis_id program
+  fic gcov use and move it into the Makefile
+  FAQ: update things that have changed
+
+Thierry Vignaud:
+  switch to '==' in raid-devfs.sh
+
+
+Summary of changes from v067 to v068
+============================================
+
+Greg Kroah-Hartman:
+  add EXTRAS documentation to the README file.
+  Always open the cdrom drive in non-blocking mode in cdrom_id
+  cdrom_id: change err() to info() to help with debugging problems
+
+Kay Sievers:
+  cleanup some debug output and move to info level + unify select() loops
+  move udevmonitor to /usr/sbin
+  ENV{TEST}=="1" compares and ENV{TEST}="1" sets the environment
+  vol_id: fix sloppy error handling
+  fix typo in cdrom_id syslog
+  bring std(in|out|err) fd's in a sane state
+  fix printed udevmonitor header
+
+
+Summary of changes from v066 to v067
+============================================
+
+Greg Kroah-Hartman:
+  added the cdrom.h #defines directly into the cdrom_id.c file
+
+Kay Sievers:
+  update SUSE rules
+  fix make install, as we don't provide a default rule set anymore
+  fix more compiler warnings ...
+  fix udevstart event ordering, we want /dev/null very early
+  don't fail too bad, if /dev/null does not exist
+
+
+Summary of changes from v065 to v066
+============================================
+
+Greg Kroah-Hartman:
+  update gentoo rule file.
+  Created cdrom_id program to make it easier to determine cdrom types
+  added cdrom_id to the build check
+  updated gentoo rule file to handle removable ide devices.
+  changed cdrom_id exports to be easier to understand and consistant with other _id programs.
+  fix klibc build issue in cdrom_id.c
+  Change the gentoo rules to use cdrom_id instead of cdsymlink.sh
+  changed location of gentoo helper apps to be /sbin instead of in scripts dir
+  tweak the gentoo rules some more.
+
+Kay Sievers:
+  add NETLINK define for the lazy distros
+  read sysfs attribute also from parent class device
+  switch some strlcpy's to memcpy
+  allow clean shutdown of udevd
+  add flag for reading of precompiled rules
+  update distro rules files
+  add SUSE rules
+  update SUSE rules
+  add firmware_helper to load firmware
+  more distro rules updates
+  update README
+  remove example rules and put the dev.d stuff into the run_directory folder
+  trivial text cleanups
+  update SUSE rules
+  split udev_util in several files
+  update SUSE rules
+  allow logging of all output from executed tools
+  add Usage: to udevmonitor and udevcontrol
+  move some logging to the info level
+
+Thierry Vignaud:
+  fix udevinfo output
+
+
+Summary of changes from v064 to v065
+============================================
+
+Greg Kroah-Hartman:
+  Added persistent name rules for block devices to gentoo rule file.
+  Added horrible (but fun) path_id script to extras.
+  Update gentoo rules file.
+
+Kay Sievers:
+  update release notes for next version
+  add udevmonitor, to debug netlink+udev events at the same time
+  allow RUN to send the environment to a local socket
+  fix GGC signed pointer warnings and switch volume_id to stdint
+
+
+Summary of changes from v063 to v064
+============================================
+
+Andre Masella:
+  volume_id: add OCFS (Oracle Cluster File System) support
+
+Hannes Reinecke:
+  usb_id: fix typo
+  add ID_BUS to *_id programs
+  create_floppy_devices: add tool to create floppy nodes based on sysfs info
+
+Kay Sievers:
+  move code to its own files
+  make SYSFS{} usable for all devices
+  add padding to rules structure
+  allow rules to have labels and skip to next label
+  thread unknown ENV{key} match as empty value
+
+
+Summary of changes from v062 to v063
+============================================
+
+Anton Farygin:
+  fix typo in GROUP value application
+
+Greg Kroah-Hartman:
+  add 'make tests' as I'm always typing that one wrong...
+  Really commit the udev_run_devd changes...
+  Fixed udev_run_devd to run the /etc/dev.d/DEVNAME/ files too
+  fix position of raw rules in gentoo config file
+
+Hannes Reinecke:
+  dasd_id: add s390 disk-label prober
+  fix usb_id and let scsi_id ignore "illegal request"
+
+Kay Sievers:
+  volume_id: remove s390 dasd handling, it is dasd_id now
+  trivial fixes for *_id programs
+  IMPORT: add {parent} to import the persistent data of the parent device
+  allow multiple values to be matched with KEY=="value1|value2"
+  udevd: set incoming socket buffer SO_RCVBUF to maximum
+  remember mapped rules state
+  ata_id: check for empty serial number
+  compile dasd only on s390
+
+Ville Skyttä:
+  correct default mode documentation in udev
+
+
+Summary of changes from v061 to v062
+============================================
+
+Kay Sievers:
+  fix symlink values separated by multiple spaces
+  update RELEASE-NOTES
+  fix typo in group assignment
+  fix default-name handling and NAME="" rules
+  add WAIT_FOR_SYSFS key to loop until a file in sysfs arrives
+  fix unquoted strings in udevinitsend
+
+Summary of changes from v060 to v061
+============================================
+
+Greg Kroah-Hartman:
+  Sync up the Debian rules files
+  fix cdrom symlink problem in gentoo rules
+  Fix ChangeLog titles
+
+Kay Sievers:
+  update RELEASE-NOTES
+  we want to provide OPTFLAGS
+  rename ALARM_TIMEOUT to UDEV_ALARM_TIMEOUT
+  udevd: optimize env-key parsing
+  don't resolve OWNER, GROUP on precompile if string contains %, $
+  set default device node to /dev
+  create udevdb files only if somehting interesting happened
+  pack parsed rules list
+  replace useless defines by inline text
+  move rule matches to function
+  add usb_id program to generate usb-storage device identifiers
+  add IEEE1394 rules to the gentoo rule file
+  fake also kernel-name if we renamed a netif
+  allow OPTIONS to be recognized for /sys/modules /sys/devices events
+  switch gentoo rules to new operators
+
+
+Summary of changes from v059 to v060
+============================================
+
+Greg Kroah-Hartman:
+  Fix the gentoo udev rules to allow the box to boot properly
+
+Gustavo Zacarias:
+  Udev doesn't properly build with $CROSS
+
+Kay Sievers:
+  Keep udevstart from skipping devices without a 'dev' file
+
+Marco d'Itri:
+  #define NETLINK_KOBJECT_UEVENT
+
+
+Summary of changes from v058 to v059
+============================================
+
+Greg Kroah-Hartman:
+  Update the gentoo rule file
+  Fix udevinfo for empty sysfs directories
+  Fix makefile to allow 'make release' to work with git
+
+Hannes Reinecke:
+  udev: fix netdev RUN handling
+  udevcontrol: fix exit code
+
+Kay Sievers:
+  prepare RELEASE-NOTES
+  add ID_TYPE to the id probers
+  add -x to scsi_id to export the queried values in env format
+  store the imported device information in the udevdb
+  rename udev_volume_id to vol_id and add --export option
+  add ata_id to read serial numbers from ATA drives
+  IMPORT allow to import program returned keys into the env
+  unify execute_command() and execute_program()
+  IMPORT=<file> allow to import a shell-var style config-file
+  allow rules to be compiled to one binary file
+  fix the fix and change the file to wait for to the "bus" link
+  fix udevstart and let all events trvel trough udev
+  prepare for module loading rules and add MODALIAS key
+  remove device node, when type block/char has changed
+  Makefile: remove dev.d/ hotplug.d/ from install target
+  udevcontrol: add max_childs command
+  udevd: control log-priority of the running daemon with udevcontrol
+  udeveventrecorder: add small program that writes an event to disk
+  klibc: add missing files
+  udevinitsend: handle replay messages correctly
+  udev man page: add operators
+  udevd: allow starting of udevd with stopped exec-queue
+  klibc: version 1.0.14
+  udev: handle all events - not only class and block devices
+  volume_id: use udev-provided log-level
+  udev: clear lists if a new value is assigned
+  udev: move dev.d/ handling to external helper
+  udev: allow final assignments :=
+  udevd: improve timeout handling
+  Makefile: fix DESTDIR
+  udevd: add initsend
+  udevd: add udevcontrol
+  udevd: listen for netlink events
+
+Stefan Schweizer:
+  Dialout group fix for capi devices in the gentoo rules file
+
+Summary of changes from v057 to v058
+============================================
+
+Daniel Drake:
+  o Writing udev rules docs update
+
+Darren Salt:
+  o update cdsymlinks to latest version
+
+Greg Kroah-Hartman:
+  o remove detach_state files from the sysfs test tree
+  o Update permissions on test scripts so they will run properly now
+  o hopefully fix up the symlinks in the test directory
+  o Removed klibc/klibc.spec as it is autogenerated
+  o Added symlinks thanks to Kay's script and git hacking
+  o add Red Hat/Fedora html documenation
+  o Update Red Hat default udev rules
+
+Kay Sievers:
+  o selinux: fix handling during creation of symlinks
+  o Fedora udev.rules update
+  o libsysfs: version 2.0
+  o klibc: version 1.0.7
+
+Masanao Igarashi:
+  o Fix libsysfs issue with relying on the detach_state file to be
+
+Summary of changes from v056 to v057
+============================================
+
+<tklauser:access.unizh.ch>:
+  o fix stupid all_partitions bug
+
+Kay Sievers:
+  o add test for make -j4 to build-check
+  o klibc: version 1.0.6
+  o update Debian rules
+  o apply default permissions only for devices that will need it
+  o adapt RELEASE-NOTES
+  o udev_volume_id: fix endianess macros
+  o udev-test.pl: add test for DEVNAME export to RUN environment
+  o update the man page to reflect the recent changes
+  o export DEVNAME to RUN-key executed programs
+  o fix make -j4 and the local klibc-install
+  o update RELEASE-NOTES
+  o add RUN key to be able to run rule based notification
+  o fix udevtest to print the error if logging is disabled
+  o move execute_program to utils + add action to init_device
+  o correct correction for error path for PROGRAM execution
+  o correct error path for PROGRAM execution
+  o klibc: version 1.0.5
+  o check for strlen()==0 before accessing strlen()-1
+  o allow to match against empty key values
+  o read %s{}-sysfs values at any device in the chain
+  o udev_rules.c: don't change sysfs_device while walking up the device chain
+  o klibc: strlcpy/strlcat - don't alter destination if size == 0
+  o fix klibc's broken strlcpy/strlcat
+  o udevinfo: print SYSFS attribute the same way we match it
+  o remove untrusted chars read from sysfs-values or returned by PROGRAM
+  o udevinfo: print errors to stderr instead of stdout
+  o klibc: version 1.0.4
+  o support log-priority levels in udev.conf
+  o test-suite: remove UDEV_TEST, it's not needed anymore
+  o libsysfs: remove trailing slash on SYSFS_PATH override
+
+
+Summary of changes from v055 to v056
+============================================
+
+<tklauser:access.unizh.ch>:
+  o fix header paths in udev_libc_wrapper.c
+
+Kay Sievers:
+  o udev-test.pl: use more common user/group names
+  o klibc: remove SCCS directories from the temporary klibc install
+  o udev-test.pl: add a test where the group cannot be found in /etc/passwd
+  o udev-test.pl: add check for textual uid/gid
+  o fix bad typo that prevents the GROUP to be applied
+  o udevd: don't delay events with TIMEOUT in the environment
+  o klibc: use klcc wrapper instead of our own Makefile
+  o change call_foreach_file to return a list
+
+
+Summary of changes from v054 to v055
+============================================
+
+<jkluebs:luebsphoto.com>:
+  o This patch causes the remove handler to check that each symlink actually points to the correct devnode and skip it if it does not.
+
+<pebenito:gentoo.org>:
+  o udev selinux fix
+
+<tklauser:access.unizh.ch>:
+  o The following patch fixes some warnings when compiling volume_id from udev with the -Wall compiler flag. Define _GNU_SOURCE for strnlen() and correct the path to logging.h
+  o The following patch fixes a warning when compiling chassis_id from udev with the -Wall compiler flag. There are too much conversions in the format string of sscanf(). One %d can be dropped.
+
+Greg Kroah-Hartman:
+  o fix raid rules
+  o added frugalware udev ruleset
+  o merge selinux and Kay's symlink fixes together
+
+Hannes Reinecke:
+  o volume_id: Fix label/uuid reading for reiserfs
+
+Kay Sievers:
+  o add udevstart to the RELEASE-NOTES
+  o volume_id: version 43
+  o clarify the shortcomings of %e
+  o correct rule match for devices without a physical device
+  o remove unneeded code, libsysfs does this for us
+  o add final release note
+  o add ENV{} key to match agains environment variables
+  o simplify sysfs_pair handling
+  o add a test and simplify debug statement
+  o support =, ==, !=, += for the key match and assignment
+  o add OPTION="last_rule" to skip any later rule
+  o rename namedev_dev to udev_rule
+  o correct enum device_type
+  o remove udevstart on make clean
+  o volume_id: version 42
+  o volume_id: version 41
+  o remove unneeded include
+  o The path to dlist.h is not correct
+  o udevinfo -d: use '=' as separator, cause ':' may be a part of the devpath
+  o klibc: version 1.0.3
+  o add RELEASE-NOTES file
+  o test suite: move "driver" link to physical device
+  o remove PLACE key match
+  o don't lookup "root" in the userdb
+  o fix ia64 compile
+  o fix segfaulting udev while DRIVER matching
+  o cleanup list.h
+  o klibc: version 0.214
+  o rename device_list->list to device_list->node
+  o replace strncpy()/strncat() by strlcpy()/strlcat()
+  o split udev and udevstart
+  o udev_volume_id: version 39
+  o rename LOG to USE_LOG in all places
+  o remove Makefile magic for klibc integration
+  o klibc_fixups: remove no longer needed stuff
+  o udev_volume_id: volume_id v38
+  o use numeric owner/group as default values to avoid parsing userdb
+  o fix up segfaulting binaries with new klibc
+  o udevinfo -d: speed-up device dump
+  o klibc: version 0.211
+  o klibc_fixups: remove unneeded stuff
+  o replace weird defines by real code
+  o udev-test.pl: remove useless tests
+  o allow unlimitied count of symlinks
+  o unmap db-file after use
+  o remove typedef for call_foreach_file() handler function
+  o correct udev_init_device
+  o rename attributes to options
+  o kill stupid gcc4 warning
+  o trivial clenaup of namedev code
+  o klibc: check for gcc4
+  o klibc: update v0.205
+
+Thierry Vignaud:
+  o gentoo rule update for raid devices
+
+
+Summary of changes from v053 to v054
+============================================
+
+<tklauser:access.unizh.ch>:
+  o udev_volume_id: add Reiser4 support
+
+Kay Sievers:
+  o namedev: skip backslashes only if followed by newline
+  o wait_for_sysfs: add joydev
+  o udevinfo: print devpath -> node relationship for all devices
+  o trivial rename of some variables
+  o klibc v0.199
+  o big libsysfs diet (pre 2.0 version)
+  o udev_volume_id: volume_id v35
+  o add "serio" to bus list
+  o determine device type in udev_init_device()
+  o move kernel name/number evaluation into udev_init_device()
+  o detect NAME="" as ignore_device rule
+  o trivial namedev cleanup
+  o cleanup db functions
+  o clean up match_place()
+  o switch device type to enum
+  o switch major/minor to dev_t
+  o remove the device node only if the major/minor number matches
+  o libsysfs: work around a klibc bug
+  o introduce OPTIONS=ignore_device, ignore_remove, all_partitions" key
+  o namedev: execute PROGRAM only once and not possibly for every physical device
+
+Patrick Mansfield:
+  o update scsi_id to work with libsysfs changes
+
+
+Summary of changes from v052 to v053
+============================================
+
+Greg Kroah-Hartman:
+  o fix gentoo fb permission issue
+  o allow simple-build-check.sh to go faster if MAKEOPTS is set
+  o make the release tarballs have writable files in them
+  o remove gentoo permission file as it's not valid anymore
+
+Kay Sievers:
+  o fix special file mode mask for temporary device node
+  o udevstart: simplify "dev" file searching
+  o udev_volume_id: remove temporary node creation and parent handling
+  o add %P modifier to query the node name of the parent device
+  o udev_volume_id: remove __packed__ from dasd structure as it does not work
+  o create /block/*/range count of partitons for all_partitions
+
+Patrick Mansfield:
+  o scsi_id changes for use with udev %N and %p
+
+
+Summary of changes from v051 to v052
+============================================
+
+<md:linux.it>:
+  o debian: update rules files
+  o raid-devfs.sh: devfs names for hardware RAID controllers
+  o scsi_id: when udevstart is started, /tmp is not writeable
+  o cdsymlinks.sh: trivial fix, the variable is initialized to '', not 0
+
+<sschweizer:gmail.com>:
+  o gentoo/udev.rules: add default permissions for sound devices
+
+Greg Kroah-Hartman:
+  o fix example comment in ide-devfs.sh
+  o Add infiniband to gentoo rules
+  o Another gentoo fix, adding dvb support
+  o Fix gentoo bug #76056 (fb device group permissions.)
+  o Fix gentoo bug #81102, device nodes for the pktcdvd device
+
+Kay Sievers:
+  o provide temporary device node for callouts to access the device
+  o udev_volume_id: fix dasd disklabel reading with -l option
+  o udev_volume_id: volume_id version 034
+  o udev_volume_id: rename probe_ibm into probe_dasd
+  o udev_volume_id: volume_id version 032
+  o Makefile: add some more warnings and prepare for clean gcc4 compile
+  o Makefile: cleanup conditional config option sections
+  o fix -Wsign-compare warnings
+  o chassis_id: clean compilation and fix bad function parameter passing
+  o simple_build_check: make it possible to pass KERNEL_DIR
+  o selinux: cleanup udev integration
+
+Michael Buesch:
+  o trivial: remove _all_ trailing slashes with no_trailing_slash()
+  o trivial: fix signedness
+  o namdev: allow symlink-only rules to specify node permissions
+  o udevd: fix valgrind warning
+
+
+Summary of changes from v050 to v051
+============================================
+
+<roland:digitalvampire.org>:
+  o This fixes a silly mistake in how udevinfo prints the major and minor numbers (right now it prints the minor next to "MAJOR" and the major next to "MINOR" ;)
+
+<tklauser:access.unizh.chbk>:
+  o I tried to compile udev 050plus with the GCC 4.0 snapshot 200412119 and got two errors about possibly uninitialized structs, so I fixed this. 
+
+Christian Bornträger:
+  o udev_volume_id: fix -d option
+
+Greg Kroah-Hartman:
+  o gentoo fb permission fix
+  o fix gcc 2.96 issue in libsysfs
+  o remove the lfs startup script on request of the author
+  o clean up the aoe char device rules, and delete the block one as it's not needed
+  o add aoe block and char device rules to the gentoo rule file
+  o fix udev_volume_id build error
+
+Hannes Reinecke:
+  o rearrange link order in Makefile
+
+Kay Sievers:
+  o udev_volume_id: new version of volume_id
+  o klibc: update to version 0.198
+  o udev_volume_id: fix FAT label reading
+  o klibc: update to version 0.196
+  o udevd: throttle the forking of processes
+  o udevd: add possible initialization of expected_seqnum
+  o udevd: it's obviously not the brightest idea to exit a device node manager if it doesn't find /dev/null
+  o udevd: separate socket handling to prepare for other event sources
+  o udevd: support -d switch to become a daemon
+  o udev_volume_id: version 27
+  o udevd: split up message receiving an queueing
+  o remove useless warning if udev.conf contains keys not read by udev itself
+  o improve event sequence serialization
+  o remove udevsend syslog noise on udevd startup
+  o limit the initial timeout of the udevd event handling
+  o correct detection of hotplug.d/ udevsend loop
+  o correct log statement
+  o remove default_* permissions from udev.conf file
+  o update Fedora config files and add some more tests
+  o allow permissions only rules
+  o add SUBSYSTEM rule to catch all block devices and apply the disk permissions
+  o update Fedora config files
+  o handle renamed network interfaces properly if we manage hotplug.d/
+  o allow multiline rules by backslash at the end of the line
+  o add OnStream tape drive rules
+  o simplify rules file by setting default mode to 0660
+  o simplify permission application
+  o I broke the extras/ again. Add simple build test script now
+  o Merge vrfy.org:/home/kay/src/udev into vrfy.org:/home/kay/src/udev.kay
+  o initial merge of fedora udev.permissions into udev.rules
+  o remove permissions file mentioning from the udev man page
+  o fix some typos in gentoo's udev.rules introduced by the merge
+
+Michael Buesch:
+  o The attached patch fixes the code path if namedev_name_device() fails
+
+Summary of changes from v049 to v050
+============================================
+
+<harald:redhat.com>:
+  o selinux patch
+
+<tklauser:access.unizh.ch>:
+  o I made some more changes to the manpage of udev including
+
+Kay Sievers:
+  o update libsysfs to CVS version and fix segfaulting attribute reading
+  o klibc supports LOG_PID now, so remove our own implementation
+  o avoid building klibc test programs and pass SUBDIRS= to klibc clean
+
+
+Summary of changes from v048 to v049
+============================================
+
+Greg Kroah-Hartman:
+  o fix 'make clean' error in klibc
+
+Kay Sievers:
+  o update klibc to 0.194
+  o export DEVNAME regardless of the state of udev_dev_d
+  o add class specific files for class/spi_transport and class/spi_host
+  o udevd-test.pl: remove wrong date calculation
+  o check earlier if we should run as udevstart
+  o remove double initialization
+  o include missing header to udevtest.c
+  o add -V option to udev to print the version number
+  o prevent udev node creatinon for "class" registration
+  o udevd: serialization of the event sequence of a chain of devices
+  o add a class/fc_host file to the list of what to wait for
+  o udev_volume_id: links sysfs.a instead of all objects
+
+Martin Schlemmer:
+  o remove leftover from udevinfo's -d option
+
+
+Summary of changes from v047 to v048
+============================================
+
+Greg Kroah-Hartman:
+  o fix udev_volume_id so it will now build properly
+  o fix scsi_id build errors due to changes in the main udev makefile
+
+
+Summary of changes from v046 to v047
+============================================
+
+<klauser:access.unizh.ch>:
+  o Various typos and other litte errors in udev.8.in
+
+<sjoerd:spring.luon.net>:
+  o DEVNAME on device removal
+
+<sschweizer:gmail.com>:
+  o Allow GROUP to have modifiers in it
+
+Greg Kroah-Hartman:
+  o add more debian rules files
+  o move distro specific config files into their own directories
+  o update debian rules files
+  o added asterix rules to the gentoo file
+  o use udevstart for udev.init.* files
+  o delete a bunch of files no longer needed
+  o fix gentoo scsi cdrom rule
+  o Fix the multithreaded build again
+  o merge
+  o comment out ability to run udev-test.pl with valgrind
+  o fix spurious valgrind warning in udev
+  o fix udevinfo '-q path' option as it was not working
+  o merge
+  o fix parallel build error
+
+Kay Sievers:
+  o update Fedora dev.d/ example and remove unused conf.d/ directory
+  o don't install distribution specific init script on "make install"
+  o restore OWNER/GROUP assignment in rule coming from RESULT
+  o make gcov compile scripts working with recent gcc
+  o fix udev-test/udev-test.pl to work with again
+  o add net/atml and class/ppdev to the wait_for_sysfs exception list
+  o add net/nlv* devices to the exception list
+  o add "pcmcia" and "fc_transport" to the wait_for_sysfs lists
+  o remove unused timestamp field
+  o simplify permission handling
+  o handle /etc/hotplug.d/ only if the event comes from udevd
+  o trivial cleanups and change some comments
+  o remove unused variables
+  o udevsend/udevd handle events without a subsystem
+  o use blacklist on device "remove" and remove dev.d/ call code duplication
+  o update the man pages and correct Usage: hints
+  o don't call the hotplug scripts with a test run
+  o don't call dev.d/ scripts twice, if directory = subsystem
+  o remove archive file if we changed something
+  o link archive insted of objects
+  o rename udev_lib to udev_utils and dev_d to udev_multiplex
+  o handle whole hotplug event with udevd/udev
+  o integrate wait_for_sysfs in udev
+  o make the searched multiplex directories conditionally
+  o add MANAGED_EVENT to the forked udev environment
+  o export DEVNAME on remove event
+  o export udev_log flag to the environment
+  o remove my test code
+  o add support for /devices-devices without any file to wait for
+  o Patch from Alex Riesen <raa.lkml@gmail.com>
+  o add a bunch of busses to the list of what to wait for
+  o close connection to syslog in forked udevd child
+  o udevd exit path cleanup
+  o fix network device naming bug
+
+
+Summary of changes from v045 to v046
+============================================
+
+Greg Kroah-Hartman:
+  o make spotless for releases
+
+Kay Sievers:
+  o Don't try to print major/minor for devices without a dev file
+  o remove get_device_type and merge that into udev_set_values()
+  o prevent udevd crash if DEVPATH is not set
+  o add ippp and bcrypt to the exception lists of wait_for_sysfs
+  o let klibc add the trailing newline to syslog conditionally
+  o disable logging for udevstart
+  o add NAME{ignore_remove} attribute
+  o remove historical SYSFS_attr="value" format
+  o don't wait for sysfs if the kernel(2.6.10-rc2) tells us what not to expect
+  o change key names in udevinfo sysfs walk to match the kernel
+  o support DRIVER as a rule key
+  o support SUBSYSTEM as a rule key
+  o rename udevdb* to udev_db*
+  o Make dev.d/ handling a separate processing stage
+  o make the udev object available to more processing stages
+  o remove udev_lib dependency from udevsend, which makes it smaller
+  o add ACTION to udev object to expose it to the whole process
+  o make udevinfo's -r option also workimg for symlink queries
+  o let udev act as udevstart if argv[1] == "udevstart"
+  o improve udevinfo sysfs info walk
+  o add sysfs info walk to udevinfo
+  o pass the whole event environment to udevd
+  o replace tdb database by simple lockless file database
+
+
+Summary of changes from v044 to v045
+============================================
+
+Martin Schlemmer:
+  o Some updates for Gentoo's udev rules
+
+
+Summary of changes from v043 to v044
+============================================
+
+Greg Kroah-Hartman:
+  o add cdsymlinks.sh support to gentoo rules file
+  o fix gentoo legacy tty rule
+  o remove 'sudo' usage from the Makefile
+  o make udev-test.pl test for root permissions before running
+
+Kay Sievers:
+  o reduce syslog noise of udevsend if multiple instances try to start udevd
+  o add i2c-dev to the list of devices without a bus
+
+
+Summary of changes from v042 to v043
+============================================
+
+Greg Kroah-Hartman:
+  o add test target to makefile
+  o add dumb script to show all sysfs devices in the system
+
+Kay Sievers:
+  o Shut up wait_for_sysfs class/net failure messages, as it's not possible to
+    get that right for all net devices. Kernels later than 2.6.10-rc1 will
+    handle that by carrying the neccessary information in the hotplug event.  
+  o wait() for specific pid to return from fork()
+  o Don't use any syslog() in signal handler, cause it may deadlock
+  o Add support for highpoint ataraid to volume_id to suppress label reading on raid set members.
+  o Add a bunch of devices without "device" symlinks
+  o Exit, if udevtest cannot open the device (segfault)
+  o Patches from Harald Hoyer <harald@redhat.com>
+  o Apply the default permissions even if we found a entry in the permissions
+    file. Correct one test, as the default is applied correctly now and the
+    mode will no longer be 0000.
+  o add test for format chars in multiple symlinks to replace
+  o Add net/vmnet and class/zaptel to the list of devices without physical device
+
+
+Summary of changes from v040 to v042
+============================================
+
+Greg Kroah-Hartman:
+  o add inotify to the rules for gentoo
+
+Kay Sievers:
+  o skip waiting for device if we get a bad event for class creation and not for a device underneath it
+  o add net/pan and net/bnep handling
+  o switch wait for bus_file to stat() instead of open() add net/tun device handling add ieee1394 device handling
+  o Remove the last klibc specific line from the main udev code Move _KLIBC_HAS_ARCH_SIG_ATOMIC_T to the fixup file which is automatically included by the Makefile is we build with klibc
+  o ignore *.rej files from failed patches
+  o update to libsysfs 1.2.0 and add some stuff klib_fixup Now we have only the sysfs.h file different from the upstream version to map our dbg() macro.
+  o improve klibc fixup integration
+  o cleanup udevd/udevstart
+  o expose sysfs functions for sharing it
+
+
+Summary of changes from v039 to v040
+============================================
+
+<jk:blackdown.de>:
+  o wait_for_sysfs update for dm devices
+
+Greg Kroah-Hartman:
+  o sparse cleanups on the tree
+  o fix stupid cut-and-paste error for msr devices on gentoo boxes
+  o add *~ to bk ignore list
+  o delete udevruler.c as per Kay's request
+  o fix up the wait_for_sysfs_test script a bit
+
+Kay Sievers:
+  o fix debug in volume id / fix clashing global var name
+  o volume_id fix
+  o $local user
+  o cleanup netif handling and netif-dev.d/ events
+  o big cleanup of internal udev api
+  o don't wait for dummy devices
+  o close the syslog
+  o Fix ppp net devices in wait_for_sysfs
+  o Fix wait_for_sysfs messages (more debugging info)
+
+
+Summary of changes from v038 to v039
+============================================
+
+Greg Kroah-Hartman:
+  o Hopefully fix the vcs issue in wait_for_sysfs
+  o take out & from wait_for_sysfs_test that I previously missed
+  o add very nice cdsymlinks scripts
+  o add some helper scripts for dvb and input devices
+  o add debian config files
+  o let the extras/ programs build "pretty" also
+  o tweak the ccdv program to handle files in subdirectories being built
+  o crap, I messed up the 'sed' instances pretty badly, this fixes the config and man page mess
+  o fix broken 'make -j5' functionality
+
+Kay Sievers:
+  o swich attribute open() to simple stat()
+  o wait_for_sysfs update for /class/firmware and /class/net/irda devices
+  o fix unusual sysfs behavior for pcmcia_socket
+  o remove sleeps from udev as it is external now
+  o delete udevruler?
+  o Makefile fix
+
+Patrick Mansfield:
+  o update udev to scsi_id 0.7
+  o pass SYSFS setting down for extras builds
+  o move assignments past local variables
+
+
+Summary of changes from v037 to v038
+============================================
+
+<andrew.patterson:hp.com>:
+  o Re: Problem parsing %s in udev rules
+
+Greg Kroah-Hartman:
+  o fix up error in building extras and libsysfs
+
+Summary of changes from v036 to v037
+============================================
+
+<md:linux.it>:
+  o small udev patch
+
+Greg Kroah-Hartman:
+  o fix compilation warning in tdb log message
+  o Fix build error with klibc due to recent changes
+  o merge
+  o add wait_for_sysfs test script to the tarball to help people debug their boxes
+  o add ipsec to wait_for_sysfs ignore list
+  o added ccdv to bk ignore list
+  o a few more Makefile tweaks for the quiet feature
+  o Make the build silent, thanks to a helper program from ncftp
+  o rename files to have '_' instead of '-' in them
+  o change max time to wait in wait_for_sysfs to 10 seconds to hopefully handle some slow machines
+  o add support for class/raw/ to wait_for_sysfs
+  o fix up Makefile for wait_for_sysfs udev_version.h dependancy
+  o remove the debian specific file, as they don't want to share with the rest of the world :(
+
+Kay Sievers:
+  o prevent deadlocks on an corrupt udev database
+  o wait_for_sysfs_update
+
+Michael Buesch:
+  o fix asmlinkage
+  o fix incompatible pointer type warning
+
+
+Summary of changes from v035 to v036
+============================================
+
+Greg Kroah-Hartman:
+  o add the error number to the error message in wait_for_sysfs to help out in debugging problems
+
+Summary of changes from v034 to v035
+============================================
+
+Greg Kroah-Hartman:
+  o added ieee1394 support to wait_for_sysfs
+  o update wait_for_sysfs with a bunch more devices thanks to user reports
+
+Summary of changes from v033 to v034
+============================================
+
+Kay Sievers:
+  o wait_for_sysfs bluetooth class update
+
+Greg Kroah-Hartman:
+  o add comment in wait_for_sysfs to explain the structure better
+  o Revert previous dev_d.c change, it's not what is causing HAL problems
+  o hm, somethings odd with DEVPATH, see if this fixes it
+  o 33_bk mark for the makefile
+  o wait_for_sysfs: clean up the logic for the list of devices that we do not expect device symlinks for
+  o get rid of annoying extra lines in the syslog for some libsysfs debug messages
+  o added support for i2c devices in wait_for_sysfs.c
+  o add support for i2c-adapter devices to wait_for_sysfs.c
+
+Summary of changes from v032 to v033
+============================================
+
+<harald:redhat.com>:
+  o udev close on exec
+  o some cleanups and security fixes
+  o some cleanups and security fixes
+  o selinux for udev
+  o cleanup PATCH for extras/chassis_id/Makefile
+
+<kpfleming:backtobasicsmgmt.com>:
+  o respect prefix= setting in built udev.conf (updated)
+
+Greg Kroah-Hartman:
+  o add support for usb interfaces to wait_for_sysfs to keep it quiet
+  o enable native tdb spinlocks on i386 platforms
+  o delete extras/multipath-tools as per the author's request
+  o be paranoid in dev_d.c
+  o add USE_SELINUX to README documentation so people have a chance to see what is going on
+  o update the selinux.h file to start to look sane
+  o update bk ignore list for the wait_for_sysfs binary
+  o kdetv wants to see device nodes in /dev
+  o update comments in scsi-devfs.sh
+  o fix up Makefiles to get the klibc build working properly
+  o update bk ignore list for new klibc generated files
+  o oops forgot to add the new klibc/include directory
+  o update klibc to version 0.181
+
+Kay Sievers:
+  o fix problems with dev.d and udevstart
+  o wait_for_sysfs debug cleanup
+  o fix problems using scsi_id with udevstart
+  o update volume_id
+  o finally solve the bad sysfs-timing for all of us
+  o volume-id build fix and update
+  o switch udev's seqnum to u64
+  o add enum tests
+  o fix udev segfaults with bad permissions file
+
+Patrick Mansfield:
+  o update udev to include scsi_id 0.6
+
+
+Summary of changes from v031 to v032
+============================================
+
+<harald:redhat.com>:
+  o udev parse bug
+
+Kay Sievers:
+  o handle only block and class devices
+  o fix udevstart badly broken in udev 031
+
+
+Summary of changes from v030 to v031
+============================================
+
+<arun:codemovers.org>:
+  o udev - read long lines from config files overflow fix
+
+<ballarin.marc:gmx.de>:
+  o Update the FAQ with info about hardlink security
+
+<david:fubar.dk>:
+  o compatibility symlinks for udev
+
+David Weinehall:
+  o Minor POSIX-fixes for udev
+
+Greg Kroah-Hartman:
+  o add symlink for video rule
+  o add a "first" list to udevstart and make it contain the class/mem/ devices
+  o fix compiler warning in udevtest.c
+  o Fix old-style pty breakage in rules file for tty device
+  o add rules for i386 cpu devices
+  o add permission for legotower usb devices
+
+Kay Sievers:
+  o Fix naming ethernet devices in udevstart
+  o update udev_volume_id
+  o let /sbin/hotplug execute udev earlier
+  o pass SEQNUM trough udevd
+  o fix manpages based on esr's spambot
+
+Martin Schlemmer:
+  o add microcode rule to permissions.gentoo file
+
+Michael Buesch:
+  o Try to provide a bit of security for hardlinks to /dev entries
+
+Olaf Hering:
+  o udevsend depends on udev_lib.o
+
+Tom Rini:
+  o fix UDEV_NO_SLEEP
+  o clean up start_udev a bit
+  o Make udev/udevstart be one binary
+  o Add 'asmlinkage' to udev-030
+
+
+Summary of changes from v029 to v030
+============================================
+
+Greg Kroah-Hartman:
+  o fix stupid off-by-one bug that caused udevstart to die on x86-64 boxes
+
+
+Summary of changes from v028 to v029
+============================================
+
+Greg Kroah-Hartman:
+  o add permission rule for jogdial device
+  o fix dumb bug I added to udevstart
+  o make a "last list" of devices for udevstart to operate on last
+  o fix permission problem with input event and ts nodes for gentoo
+  o change default perms of misc/rtc to be readable by anyone
+
+Olaf Hering:
+  o allow NAME_SIZE > SYSFS_PATH_MAX
+
+
+Summary of changes from v027 to v028
+============================================
+
+<atul.sabharwal:intel.com>:
+  o Patch for chassis_id exras module
+
+Daniel Drake:
+  o Writing udev rules doc update
+
+Greg Kroah-Hartman:
+  o clean up block whitelist search logic a bit
+  o reverse order of scanning of udevstart to look at class before block
+
+Kay Sievers:
+  o update udev_volume_id
+
+Leann Ogasawara:
+  o udevstart performance increase
+
+Patrick Mansfield:
+  o update udev scsi_id to scsi_id 0.5
+
+
+Summary of changes from v026 to v027
+============================================
+
+<fork0:users.sf.net>:
+  o fix handle leak in udev_lib.c
+
+Greg Kroah-Hartman:
+  o tweak the gentoo default permission rules as they are wrong for tty and misc devices
+
+
+Summary of changes from v025 to v026
+============================================
+
+Arnd Bergmann:
+  o udev rpm fix
+
+Greg Kroah-Hartman:
+  o add test for ! in partition name
+  o 025_bk mark
+  o Update to version 117 of klibc (from version 108)
+  o add volume_id ignore rule for bk
+  o add volume_id support to the udev.spec file
+  o remove dbus and selinux stuff from the udev.spec file
+  o delete udev_selinux as it doesn't work properly and is the wrong way to do it
+  o Deleted the udev_dbus extra as it didn't really work properly and HAL has a real solution now
+  o add udev.permissions.slackware file
+  o udevstart: close open directories
+
+Kay Sievers:
+  o fix udevd zombies
+  o catchup with recent klibc
+  o Re: udevsend fallback
+  o udev_volume_id update
+  o udev callout for reading filesystem labels
+  o udev callout for reading filesystem labels
+  o udev default config layout changes
+
+Leann Ogasawara:
+  o evaluate getenv() return value for udev_config.c
+
+Summary of changes from v024 to v025
+============================================
+
+<md:linux.it>:
+  o devfs.sh-ide-floppy
+
+<sjoerd:spring.luon.net>:
+  o DEVNODE -> DEVNAME transition fixes
+
+Daniel Drake:
+  o Update writing udev rules docs
+
+Greg Kroah-Hartman:
+  o make dev.d call each directory in the directory chain of the device name, instead of just the whole name
+  o add devd_test script
+  o add more permissions based on SuSE's recommendations
+  o added rules for tun and raw devices
+  o add udev conf.d file
+  o Switch the default config to point to a directory for the rules and permission files
+  o update the Red Hat .dev files to work on other distros
+  o add dbus.dev, pam_console.dev and selinux.dev files for /etc/dev.d/default/ usage
+  o add hints for red hat users from Leann Ogasawara <ogasawara@osdl.org>
+  o add scripts to run gcov for udev from Leann Ogasawara <ogasawara@osdl.org>
+  o change permissions on udevd test scripts
+  o Fix build process for users who have LC_ALL set to a non-english language
+  o Added expanded tests to the test framework from Leann Ogasawara <ogasawara@osdl.org>
+  o added execelent "writing udev rules" document from Daniel Drake <dan@reactivated.net>
+  o added rule to put USB printers in their proper places
+  o added rules for CAPI devices
+  o added a dev.d alsa script to help people out
+
+Kay Sievers:
+  o fix test regressions
+  o udev_selinux changes
+  o udevd test script
+  o udev_dbus changes
+  o fix devpath for netdev
+
+Leann Ogasawara:
+  o gcov for udev
+
+
+Summary of changes from v023 to v024
+============================================
+
+<atul.sabharwal:intel.com>:
+  o Add README for chassis_id
+  o Add chassis_id program to extras directory
+
+<chris_friesen:sympatico.ca>:
+  o udevd race conditions and performance,  assorted cleanups
+
+<hare:suse.de>:
+  o fix SEGV in libsysfs/dlist.c
+
+<maryedie:osdl.org>:
+  o add OSDL documentation for persistent naming
+
+<md:linux.it>:
+  o small ide-devfs.sh fix
+
+Greg Kroah-Hartman:
+  o remove compiler warning from udevd.c
+  o only generate udev.8 on the fly, not all other man pages
+  o update bk ignore list some more
+  o update bk ignore list
+  o switch to generate the man pages during the normal build, not during the install
+  o convert udev.8.in to use @udevdir@ macro for make install
+  o first step of making man pages dynamically generated
+  o add install and uninstall the etc/dev.d/net/hotplug.dev file to the Makefile
+  o tweak net_test a bit
+  o fix some segfaults when running udevtest for network devices
+  o make a net_test test script using udevtest
+  o handle the subsytem if provided in udevtest
+  o add hotplug.dev script to handle renamed network devices
+  o add a bunch of network class devices to the test sysfs tree
+  o add udevruler to the bk ignore list
+  o update RFC-dev.d docs due to DEVNODE to DEVNAME change
+  o clean up chassis_id coding style
+  o clean up the OSDL document formatting a bit
+  o add netlink rules to devfs and gentoo rules files
+  o added USB device rules to rules files
+  o clean up the gentoo rules file a bit more, adding dri rules
+  o fix up udev.rules to handle oss rules better
+  o 023_bk mark
+  o fix udev.spec file for where udevtest should be placed
+
+Kay Sievers:
+  o tweak node unlink handling
+  o switch udevd's msg_dump() to #define
+  o handle netdev in udevruler
+  o man page cleanup
+  o put config info in db for netdev
+  o increase udevd event timeout
+  o udevstart fix
+  o put netdev handling and dev.d/ in manpages
+  o DEVPATH for netdev
+  o netdev - udevdb+dev.d changes
+  o udevd race conditions and performance,  assorted cleanups - take 2
+  o udevinfo patch
+  o dev_d.c file sorting and cleanup
+  o apply all_partitions rule to main block device only
+
+
+Summary of changes from v022 to v023
+============================================
+
+Kay Sievers:
+  o hmm, handle net devices with udev?
+  o correct apply_format() for symlink only rules
+  o don't init namedev on remove
+  o first stupid try for a rule compose gui
+  o replace fgets() with mmap() and introduce udev_lib.[hc]
+  o make udevtest a real program :)
+
+Daniel E. F. Stekloff:
+  o udevinfo patch
+
+Greg Kroah-Hartman:
+  o create the /etc/dev.d/ directories in 'make install'
+  o actually have udev run files ending in .dev in the /etc/dev.d/ directory as documented
+  o added RFC-dev.d document detailing how /etc/dev.d/ works
+  o fixed up udev.spec to handle selinux stuff properly now
+  o remove USE_DBUS and USE_SELINUX flags from the README as they are no longer present
+  o remove selinux stuff from the main Makefile
+  o move udev_selinux into extras/selinux
+  o fix dbus build in the udev.spec file
+  o remove dbus stuff from main Makefile
+  o move udev_dbus to extras/dbus
+  o udev_dbus can now compile properly, but linnking is another story
+  o remove udev_dbus.h from Makefile
+  o first cut at standalone udev_selinux program
+  o remove selinux support from udev core as it's no longer needed
+  o first cut at standalone udev_dbus program
+  o add get_devnode() helper to udev_lib for udev_dbus program
+  o remove dbus code from core udev code as it's no longer needed to be there
+  o add /etc/dev.d/ support for udev add and remove events
+  o fix build error in namedev.c caused by previous patch
+  o 022_bk tag
+  o fix 'make spotless' to really do that in klibc
+  o add a question/answer about automounting usb devices to the FAQ
+  o mark scsi-devfs.sh as executable
+  o Increase the name size as requested by Richard Gooch <rgooch@ras.ucalgary.ca>
+  o fix udevtest to build properly after the big udev_lib change
+
+Olaf Hering:
+  o uninitialized variable for mknod and friend
+
+Richard Gooch:
+  o SCSI logical and physical names for udev
+
+Theodore Y. T'so:
+  o Trivial man page typo fixes to udev
+
+
+Summary of changes from v021 to v022
+============================================
+
+<ananth:in.ibm.com>:
+  o more Libsysfs updates
+  o Libsysfs updates
+
+<async:cc.gatech.edu>:
+  o fix HOWTO-udev_for_dev for udevdir
+
+Kay Sievers:
+  o udev-test.pl cleanup
+  o add dev node test to udev-test.pl
+  o add permission tests
+  o "symlink only" test
+  o callout part selector tweak
+  o cleanup callout fork
+  o allow to specify node permissions in the rule
+  o man page beauty
+  o put symlink only rules to the man page
+  o rename strn*() macros to strmax
+  o conditional remove of trailing sysfs whitespace
+  o clarify udevinfo text
+  o better fix for NAME="foo-%c{N}" gets a truncated name
+  o overall trivial trivial cleanup
+  o fix NAME="foo-%c{N}" gets a truncated name
+  o cleanup mult field string handling
+
+<ken:cgi101.com>:
+  o fix a type in docs/libsysfs.txt
+  o Added line to udev.permissions.redhat
+  o Include more examples in the docs area for gentoo and redhat
+
+<md:linux.it>:
+  o udevstart fixes
+
+Greg Kroah-Hartman:
+  o add big major tests to udev-test.pl
+  o add a test for a minor over 255
+  o udev-test.pl: print out major:minor and perm test "ok" if is ok
+  o make perm and major:minor test errors be reported properly
+  o remove extra ; in namedev_parse.c
+  o Added multipath-tools 0.1.1 release
+  o deleted current extras/multipath directory
+  o 021_bk mark
+  o fix the build for older versions of gcc
+
+Hanna V. Linder:
+  o Small fix to remove extra "will" in man page
+
+Olaf Hering:
+  o make spotless
+  o udev* segfaults with new klibc
+
+Patrick Mansfield:
+  o add tests for NAME="foo-%c{N}"
+
+Summary of changes from v020 to v021
+============================================
+
+Kay Sievers:
+  o install udevinfo in /usr/bin
+  o blacklist pcmcia_socket
+
+Greg Kroah-Hartman:
+  o fix udev.spec to find udevinfo now that it has moved to /usr/bin
+  o Fix another problem with Makefile installing initscript
+  o fix the Makefile to install the init script into the proper directory
+  o make spec file turn off selinux support by default
+
+
+Summary of changes from v019 to v020
+============================================
+
+<christophe.varoqui:free.fr>:
+  o multipath update
+
+Kay Sievers:
+  o man page udevstart
+  o cleanup udevstart
+  o bugfix for local user
+  o unlink bugfix
+  o TODO update
+  o clarify udevinfo device walk
+  o udevinfo symlink reverse query
+  o fix stroul endptr use
+  o add $local user spport for permissions
+  o udev - man page update
+  o udev - fix debug info for multiple rule file config
+  o udev - kill udevd on install
+  o udev - activate formt length attribute
+  o udev - safer sprintf() use
+
+<md:linux.it>:
+  o no error on enoent
+  o escape dashes in man pages
+  o remove usage of expr in ide-devfs.sh
+
+<rml:ximian.com>:
+  o automatically install correct initscript
+  o update documetation for $local
+
+Andrey Borzenkov:
+  o Add symlink only rules support
+
+Greg Kroah-Hartman:
+  o update the TODO list as we already have a devfs config file
+  o make start_udev use udevstart binary
+  o install udevstart
+  o Remove Debian permission files as the Debian maintainer doesn't seem to want to share :(
+  o update the Gentoo rules files
+  o Add Red Hat rules and permissions files
+  o add udevstart to the ignore list
+  o add udevstart program based on a old patch from Harald Hoyer <harald@redhat.com>
+  o unlink the file before we try to create it
+  o Merge greg@bucket:/home/greg/src/udev into kroah.com:/home/greg/src/udev
+
+
+Summary of changes from v018 to v019
+============================================
+
+Kay Sievers:
+  o TODO update
+  o udev - correct relative symlink
+  o udev - safer string handling - part four
+  o udev - safer string handling - part three
+  o udev - safer string handling - part two
+  o udev - man page update
+  o udev - safer string handling all over the place
+  o manpage update
+  o udev - allow all files in a directory as the config
+  o udev - simple klibc textual uid/gid handling
+
+Andrey Borzenkov:
+  o do not remove real .udev.tdb during RPM build
+
+Greg Kroah-Hartman:
+  o add new TODO item about local user permissions
+  o Add initial SELinux support for udev
+  o fix build for very old versions of make
+  o remove limit of the number of args passed to PROGRAM
+  o force udev to include the internal version of libsysfs and never the external one
+  o fix up libsysfs header file usage to fix bug reports from users that have sysfsutils installed already
+  o remove udevtest on 'make clean'
+  o remove udevd priority TODO item, as it's not needed at all
+
+Patrick Mansfield:
+  o update udev scsi_id to scsi_id 0.4
+
+
+Summary of changes from v017 to v018
+============================================
+
+<ext.devoteam.varoqui:sncf.fr>:
+  o [PATCH] symlink dm-[0-9]* rule
+  o update extras/multipath
+
+<john-hotplug:fjellstad.org>:
+  o init.d debian patch
+
+Kay Sievers:
+  o udev - TODO update
+  o udev - add %s{filename} to man page
+  o udev - udevd/udevsend man page
+  o udev - switch callout part selector to {attribute}
+  o udev - switch SYSFS_file to SYSFS{file}
+  o udev - create all partitions of blockdevice
+  o allow SYSFS{file}
+  o Adding '%s' format specifier to NAME and SYMLINK
+
+Greg Kroah-Hartman:
+  o added some scsi_id files to the bk ignore file
+  o added scsi_id and some more documentation to the udev.spec file
+  o update udev.rules.gentoo with new config file format
+  o Update the Gentoo udev.rules and udev.permissions files
+  o Create a udev.rules.examples file to hold odd udev.rules
+  o add udevd priority issue to the TODO list
+  o more HOWTO cleanups
+  o add HOWTO detailing how to use udev to manage /dev
+  o mv libsysfs/libsysfs.h to libsysfs/sysfs/libsysfs.h to make it easier to use
+  o add start_udev init script
+  o add support for UDEV_NO_SLEEP env variable so Gentoo people will be happy
+  o start up udevd ourselves in the init script to give it some good priorities
+  o update the red hat init script to handle nodes that are not present
+  o add a "old style" SYSFS_attribute test to udev-test.pl
+  o Have udevsend report more info in debug mode
+  o Have udevd report it's version in debug mode
+  o fix up bug created for udevtest in previous partition creation patch
+  o update the udev.spec to add udevtest and make some more Red Hat suggested changes
+  o add ability to install udevtest to Makefile
+  o 017_bk mark
+  o Add another test to udev-test.pl and fix a bug when only running 1 test
+  o Fix bug where we did not use the "converted" kernel name if we had no rule
+
+Patrick Mansfield:
+  o udev use new libsysfs header file location
+  o udev add some ID tests
+
+
+Summary of changes from v016 to v017
+============================================
+
+<azarah:nosferatu.za.org>:
+  o make logging a config option
+
+<christophe.varoqui:free.fr>:
+  o more udev-016/extras/multipath
+  o more udev-016/extras/multipath
+  o update extras/multipath
+
+Kay Sievers:
+  o udev - keep private data out of the database?
+  o better credential patch
+  o udevd - client access authorization
+  o compile udevd with klibc
+  o udev - fix "ignore method"
+  o udev - fix cdrom symlink rule
+  o convert udevsend/udevd to DGRAM and single-threaded
+  o udevd - kill the lockfile
+  o udevd - fix socket path length
+  o udevd - switch socket path to abstract namespace
+  o udevd - allow to bypass sequence number
+  o include used function
+
+Greg Kroah-Hartman:
+  o add udev_log to the documentation
+  o fix offsetof() define in klibc
+  o add some .spec file changes from Red Hat
+  o update the init.d udev script based on a patch from Red Hat
+  o remove the .udev.tdb when installing or uninstalling to be safe
+  o remove the database at startup
+  o fix bug in permission handling
+  o update klibc to version .107
+  o update the bitkeeper ignore file list
+  o add udevtest program to build
+  o fix problem where usb devices can be either the main device or the interface
+  o more logging.h cleanups to be a bit more flexible
+  o stop using mode_t as different libcs define it in different ways :(
+  o remove some more KLIBC fixups that are no longer needed
+  o let udev-test.pl run an individual test if you ask it to
+  o Handle the '!' character that some block devices have
+  o add a block device with a ! in the name, and a test for this
+  o fix up 'make release' to use bk to build the export tree
+  o fix log option code so that it actually works for all udev programs
+  o finish syncing up with klibc
+  o sync with latest version of klibc (0.107)
+  o fix up Makefile dependancies for udev_version.h
+
+Patrick Mansfield:
+  o udev add wild card compare for ID
+  o udev kill extra bus_id compares in match_id
+
+
+Summary of changes from v015 to v016
+============================================
+
+<elkropac:students.zcu.cz>:
+  o get_dev_number() in extras/ide-devfs.sh
+
+<rrm3:rrm3.org>:
+  o FAQ udev.rules.devfs
+
+Greg Kroah-Hartman:
+  o add udevd and udevsend to the spec file
+  o make /etc/hotplug.d/default/udev.hotplug symlink point to udevsend now
+  o add KERNEL_DIR option so that the distros will be happy
+  o make udevsend binary even smaller
+  o udevsend now almost compiles with klibc, struct sockaddr_un is only problem now
+  o fix up logging code so that it can be built without it being enabled
+  o rework the logging code so that each program logs with the proper name in the syslog
+  o remove logging.c as it's no longer needed
+  o kill the last examples that contained the %D option
+  o remove a __KLIBC__ tests in libsysfs, as klibc now supports getpagesize()
+  o udevd - remove stupid locking error I wrote
+  o update to klibc version 0.101, fixing the stdin bug
+  o fix Makefile typo for USE_LSB install
+  o allow dbus code to actually build again
+
+Kay Sievers:
+  o let udevsend build with klibc
+  o udevd - config cleanup
+  o udevd - cleanup and better timeout handling
+  o fix possible buffer overflow
+  o udevd - next round of fixes
+  o udevinfo - missing options for man page
+  o udev - trivial style cleanup
+
+
+Summary of changes from v014 to v015
+============================================
+
+<mbuesch:freenet.de>:
+  o LFS init script update
+
+Greg Kroah-Hartman:
+  o update klibc to version 0.98
+  o clean up udevinfo on 'make clean'
+  o add udevinfo man page to spec file
+  o remove command line documentation from udev man page
+  o create initial version of udevinfo man page
+  o added URL to spec file
+  o add udevinfo to udev.spec file
+  o add udevinfo to install target of Makefile
+  o rip out command line code from udev, now that we have udevinfo
+  o udevinfo doesn't need to declare main_envp
+  o move get_pair to udev_config.c because udevinfo doesn't need all of namedev.o
+  o more makefile cleanups
+  o move udevinfo into the main build and clean up the main Makefile a bit
+  o clean up compiler warnings if building using klibc
+  o make udevd only have one instance running at a time
+  o new testd.block script for debugging
+  o udevsnd : clean up message creation logic a bit
+  o make bk ignore udevd and udevsend binaries
+  o whitespace cleanups
+  o remove TODO item about BUS value, as it is now done
+  o add support for figuring out which device on the sysfs "chain" the rule applies to
+
+Kay Sievers:
+  o udevinfo - now a real program :)
+  o udevd - cleanup and better timeout handling
+  o udev - next round of udev event order daemon
+  o fix udevd exec
+  o udev - udevinfo with device chain walk
+  o spilt udev into pieces
+
+
+Summary of changes from v013 to v014
+============================================
+
+<ananthmg:rediffmail.com>:
+  o libsysfs update for refresh + namedev.c changes
+
+<christophe.varoqui:free.fr>:
+  o udev-013/extras/multipath update
+
+<flamingice:sourmilk.net>:
+  o minor patch for devfs rules
+
+Kay Sievers:
+  o udev - program to query all device attributes to build a rule
+  o set default owner/group in db - update
+  o udev - reverse user query options
+  o udev - kill %D from udev-test.pl
+  o add udev logging to info log
+  o udev - mention format string escape char in man page
+
+Greg Kroah-Hartman:
+  o misc code cleanups
+  o fixup logging.h to handle different logging options properly
+  o clean up the logging patch a bit to make the option more like the other options
+  o remove the %D modifier as it is not longer needed
+  o remove unneeded keyboard rule
+  o add usb_host and pci_bus to the class blacklist
+  o added input device rules to udev.rules and udev.rules.devfs
+  o 013_bk mark
+
+Hanna V. Linder:
+  o set default owner/group in db
+  o small cut n paste error fix
+
+Patrick Mansfield:
+  o update udev scsi_id to scsi_id 0.3
+
+
+Summary of changes from v012 to v013
+============================================
+
+<eike-hotplug:sf-tec.de>:
+  o LSB init script and other stuff
+
+<elkropac:students.zcu.cz>:
+  o fix udev directory for Debian init script
+
+<tiggi:infa.abo.fi>:
+  o udev 012 old gcc fixup
+
+Christophe Saout:
+  o add IGNORE rule type
+  o small cleanup
+
+Greg Kroah-Hartman:
+  o update TODO with some new, small items
+  o Cset exclude: greg@kroah.com|ChangeSet|20040113010256|48515
+  o update the README in a few places
+  o fix -d typo in the manpage update
+  o Fix stupid gcc "optimization" of 1 character printk() calls.... Ick
+  o oops, forgot to fix up the PROGRAM result from ID to RESULT in the config files
+  o Add alsa device rules and a few other devfs rules
+  o fix a few stale comments in namedev.c
+  o convert the default rules files to the new format
+  o convert the test shell scripts to the config file format
+  o add bus test for usb-serial bus
+  o Add some helpful messages if the user uses the older config file format
+  o added dri rule to the default config file
+  o added init.d udev script for debian
+  o add a script that tests the IGNORE rule
+  o add silly script that names cdrom drives based on the cd in them
+  o add cdrom rule for ide cdrom
+  o replace list_for_each with list_for_each_entry, saving a few lines of code
+  o add a blacklist of class devices we do not want to look at
+
+Kay Sievers:
+  o fix klibc with printf() and gcc
+  o udev - small script optimization
+  o udev - introduce format escape char
+  o udev - more CALLOUT is PROGRAM now
+  o udev - CALLOUT is PROGRAM now
+  o update documentation for new config file format
+  o more advanced user query options
+  o udev - simple debug tweak
+  o udev - drop all methods :)
+  o udev - advanced user query options
+  o udev - Makefile error
+  o udev - make exec_callout() reusable
+  o udev - exec status fix for klibc
+  o fix Silly udev script
+
+
+Summary of changes from v011 to v012
+============================================
+
+<azarah:nosferatu.za.org>:
+  o make symlink work properly if there is already a file in its place
+  o Fix udev gcc-2.95.4 compat
+
+<christophe.varoqui:free.fr>:
+  o extras multipath update
+  o extras multipath update
+
+Kay Sievers:
+  o mention user callable udev + options in man page
+  o make udev user callable to query the database
+  o depend on all .h files
+  o cleanup namedev_parse debug text
+  o extend exec_program[]
+  o ide-devfs.sh update
+  o fix for apply_format()
+  o check for empty symlink string
+  o 'ide' missing in bus_files[]
+  o small trivial cleanup of latest changes
+
+<mbuesch:freenet.de>:
+  o introduce signal handler
+
+<rml:ximian.com>:
+  o udev spec file update
+
+Greg Kroah-Hartman:
+  o minor grammer fixes for the udev_vs_devfs document
+  o move the dbus config file to etc/dbus-1/system.d/
+  o move the config files to etc/udev to clean up main directory a bit
+  o add Gentoo versions of the rules and permissions files
+  o if using glibc, link dynamically, as no one like 500Kb udev binaries
+  o minor change to udev_vs_devfs document
+  o added udev vs devfs supid document to the tree
+  o move the signal handling registration to after we have initialized enough stuff
+  o make ide-devfs.sh executable in the tree
+  o udev.permissions.debian - forgot the dm nodes
+  o update the udev.permissions.debian file with new entries
+  o added udev.init script for the Linux From Scratch project
+
+
+
+Summary of changes from v010 to v011
+============================================
+
+<mbuesch:freenet.de>:
+  o proper cleanup on udevdb_init() failure
+
+<mh:nadir.org>:
+  o patch udev 009-010 rpm spec file
+
+<svetljo:gmx.de>:
+  o fix udev sed Makefile usage
+
+Greg Kroah-Hartman:
+  o add documentation about the BUS key being optional for the LABEL rule
+  o add tests for LABEL rule with a device that has no bus
+  o Don't require the BUS value for the LABEL rule
+  o If a LABEL rule has a BUS id, then we must check to see if the device is on a bus
+  o add documentation about the BUS key being optional for the CALLOUT rule
+  o If a CALLOUT rule has a BUS id, then we must check to see if the device is on a bus
+  o Don't require the BUS value for the CALLOUT rule
+  o add test for callout rule with a device that has no bus
+  o 010_bk stamp
+  o added different build options to the rpm udev.spec file
+  o add pci to the bus_files list
+  o check for empty line a bit better in the parser
+  o more init script cleanups, the stop target now calls udev to cleanup instead of just removing the whole /udev directory
+  o make udev init script run udev in the background to let startup go much faster
+  o fix long delay for all devices in namedev
+
+
+Summary of changes from v009 to v010
+============================================
+
+<ananth:in.ibm.com>:
+  o change pgsize
+
+<christophe.varoqui:free.fr>:
+  o extras multipath update
+  o extras multipath update
+  o extras multipath update
+  o extras multipath update
+
+Kay Sievers:
+  o fix udev-test.pl
+  o small cleanup udev-remove.c
+  o experimental CALLOUT script for devfs ide node creation with cd, disc, part
+  o add any valid device
+  o introduce format char 'k' for kernel-name
+  o trivial make fixes
+  o don't overwrite old config on install
+  o udev-remove.c cleanups
+  o bug in udev-remove.c
+  o trivial cleanup parser changes
+
+<roman.kagan:itep.ru>:
+  o fix comment and whitespace handling in config files
+
+Adam Kropelin:
+  o Allow build with empty EXTRAS
+
+Daniel E. F. Stekloff:
+  o libsysfs 0.4.0 patch
+  o fix scsi_id segfault with udev-009
+  o add libsysfs docs
+
+David T. Hollis:
+  o mark config files as such in the rpm spec file
+
+Greg Kroah-Hartman:
+  o fix complier warning in namedev.c
+  o add documentation for the new '%k' modifier (kernel name replacement)
+  o add documentation about the multiple sysfs values that are now allowed for the LABEL rule
+  o add tests for multi-file LABEL rules
+  o add ability to have up to 5 SYSFS_ file/value pairs for the LABEL rule
+  o Just live with a sleep(1) in namedev for now until libsysfs is fixed up
+  o try to wait until the proper device file shows up in sysfs
+  o remove unneeded TODO and FIXME entry
+  o clean up the stand-alone tests to work properly on other people's machines
+  o add tests to catch whitespace and comment config file parsing errors
+
+
+Summary of changes from v008 to v009
+============================================
+
+<christophe.varoqui:free.fr>:
+  o more extras/multipath changes
+  o and more extras/multipath updates
+  o more extras/multipath updates
+  o yet more extras/multipath
+  o more extras/multipath updates
+  o extras/multipath update
+
+<david:fubar.dk>:
+  o D-BUS patch for udev-008
+
+<eike-hotplug:sf-tec.de>:
+  o add init.d/udev to "make install"
+  o add init.d/udev to the spec file
+
+Kay Sievers:
+  o don't rely on field order in namedev_parse
+  o get part of callout return string
+  o remove '\n' from end of callout return
+  o man-page mention multiple symlinks
+  o allow multiple symlinks
+  o cleanup man & remove symlink comment
+  o experimental (very simple) SYMLINK creation
+  o man page beauty
+  o pattern match for label method
+  o a bug in linefeed removal
+
+<rml:ximian.com>:
+  o remove udev from runlevels on uninstall
+  o install initscript in udev rpm
+
+Daniel E. F. Stekloff:
+  o pre-libsysfs-0.4.0 patch
+
+Greg Kroah-Hartman:
+  o signal fixes due to klibc update
+  o sync klibc with release 0.95
+  o add mol permissions to the debian permissions file
+  o update the FAQ with info about bad modprobe events from the devfs scheme
+  o some cleanups due to the need for LABEL rules to use "SYSFS_" now
+  o Add restart target to the etc/init.d/udev script
+  o tweak the config file generation portion of the Makefile a bit
+  o change devfs disk name rule from 'disk' to 'disc'
+  o add vc support to udev.rules.devfs
+  o added a devfs udev config file from Marco d'Itri <md@Linux.IT>
+  o set default mode to 0600 to be safer
+  o Makefile tweaks for the DBUS build
+  o update the FAQ due to the latest devfs mess on lkml and also due to symlinks now working
+  o document the different Makefile config options that we have
+  o change USE_DBUS to DBUS in Makefile, and disable it by default as it's still to hard to build on all systems
+  o fix formatting of udev_dbus.c to use tabs.  Also get it to build properly now
+  o move all of the DBUS logic into one file and remove all of the #ifdef crud from the main code
+
+Olaf Hering:
+  o dump latest klibc into the udev build tree
+  o use udevdir in udev.conf
+
+Patrick Mansfield:
+  o better allow builds of extras programs under udev
+  o update udev extras/scsi_id to version 0.2
+
+
+Summary of changes from v007 to v008
+============================================
+
+<azarah:nosferatu.za.org>:
+  o more config file parsing robustness
+
+<christophe.varoqui:free.fr>:
+  o udev-007/extras/multipath update
+
+Arnd Bergmann:
+  o Build failure - missing linux/limits.h include?
+  o Add format modifier for devfs like naming
+  o klibc makefile fixes
+
+Daniel E. F. Stekloff:
+  o another patch for path problem
+  o quick fix for libsysfs bus
+  o libsysfs changes for sysfsutils 0.3.0
+
+Greg Kroah-Hartman:
+  o fix up some duplicated function compiler warnings in libsysfs
+  o fix some compiler warnings in the tdb code
+  o Added Kay's name to the man page
+  o update the wildcard documentation in the man page to show the new styles supported
+  o fix permission handling logic
+  o enable default_mode ability to actually build
+  o add support for the default_mode variable, as it is documented
+  o show permissions and groups in the label_test
+  o remove some items off of the TODO list, as they are now done
+  o fix up the tests to work without all of the environ variables
+  o get rid of the majority of the debug environment variables
+  o Update the man page to show the new config file, it's format, and how to use it
+  o fix up the tests to support the rules file name change
+  o add support for a main udev config file, udev.conf
+  o turn debugging messages off by default
+  o split out the namedev config parsing logic to namedev_parse.c
+  o rename namedev's get_attr() to be main namedev_name_device() as that's what it really is
+  o add devfs like tty rules as an example in the default config file
+  o operate on the rules in the order they are in the config file (within the rule type) instead of operating on them backwards.
+  o Cset exclude: dsteklof@us.ibm.com|ChangeSet|20031126173159|56255
+  o add test for checking the BUS value
+  o fix problem where we were not looking at the BUS value
+  o add scsi and pci bus links in the test sysfs tree
+  o add test and documentation for new %D devfs format modifier
+  o changed the default location of the database to /udev/.udev.tdb to be LSB compliant
+  o get rid of functions in klibc_fixups that are now in klibc
+  o sync up with the 0.84 version of klibc
+  o fix udev init.d script to handle all class devices in sysfs
+  o fix the test.block and test.tty scripts due to their moveing.  Also add a test.all script
+  o 007_bk version change to Makefile
+
+Kay Sievers:
+  o pattern matching for namedev
+  o catch replace device by wildcard
+  o udev.8 tweak numeric id text
+  o udev-test.pl add subdir test
+  o namedev.c strcat tweak
+  o overall whitespace + debug text conditioning
+  o udev-test.pl - tweaks
+
+Martin Hicks:
+  o Add -nodefaultlibs while compiling against klibc
+
+Olaf Hering:
+  o ARCH detection for ppc
+
+Patrick Mansfield:
+  o fix udev parallel builds with klibc
+
+
+Summary of changes from v006 to v007
+============================================
+
+<md:linux.it>:
+  o fix segfault in parsing bad udev.permissions file
+
+Greg Kroah-Hartman:
+  o update default config file with a CALLOUT rule, and more documentation
+  o updated the man page with the latest format specifier changes
+  o added ability to put format specifiers in the CALLOUT program string
+  o tweak udev-test.pl to report '0' errors if that's what happened
+  o only build klibc_fixups.c if we are actually using klibc
+  o add support for string group and string user names in udev.permissions
+  o add getgrnam and getpwnam to klibc_fixups files
+  o remove Makefile.klibc
+  o add udev-test perl script from Kay Sievers <kay.sievers@vrfy.org> which blows away my puny shell scripts
+  o added debian's version of udev.permissions
+  o change to 006_bk version
+
+Kay Sievers:
+  o format char for CALLOUT output
+  o more namedev whitespace cleanups
+  o support arguments in callout exec
+  o namedev.c - change order of fields in CALLOUT
+  o namedev.c whitespace + debug text cleanup
+  o man page with udev.permissions wildcard
+
+Olaf Hering:
+  o static klibc udev does not link against crt0.o
+
+Summary of changes from v005 to v006
+============================================
+
+<chris_friesen:sympatico.ca>:
+  o faster test scripts
+
+Arnd Bergmann:
+  o more robust config file parsing in namedev.c
+  o add bus id modifier
+
+Daniel E. F. Stekloff:
+  o patch for libsysfs sysfs directory handling
+
+Greg Kroah-Hartman:
+  o add another line to udev.permissions in the proper format
+  o tweak replace_test
+  o fix permissions to work properly now
+  o add real udev.permissions file to test directory
+  o fix namedev.c to build with older version of gcc
+  o add dumb test for all of the different modifiers
+  o update the TODO list with more items that people can easily do
+  o move the test.block and test.tty scripts to the test/ directory
+  o add remove actions to the test scripts
+  o turn DEBUG_PARSER off by default
+  o add some documentation for the %b modifier to the default config file
+  o fix make install rule for when the udev symlink is already there
+  o change release target in makefile
+  o change debug level on printf values for now
+  o updated demo config file
+  o add some documentation of the modifiers to the default config file
+  o add demo config file
+  o updated bk ignore list for klibc generated files
+  o add printf option to label test to verify it works
+  o fix up printf-like functionality due to previous changes
+  o get the major/minor number before we name the device
+  o add scsi_id "extra" program from Patrick Mansfield <patmans@us.ibm.com>
+  o Add multipath "extra" program from Christophe Varoqui, <christophe.varoqui@free.fr>
+  o trailing whitespace cleanups
+  o splig LABEL and NUMBER into separate functions
+  o add TOPO regression test
+  o move TOPOLOGY rule to it's own function
+  o fix bug where NUMBER and TOPOLOGY would not work for partitions
+  o clean up the way we find the sysdevice for a block device for namedev
+  o updated label test script (tests for partitions now.)
+  o split REPLACE and CALLOUT into separate functions
+  o add debug line for REPLACE call
+  o add replace test
+  o add more sysfs test tree files
+  o change UDEV_SYSFS_PATH environment variable due to libsysfs change
+  o fix bug in klibc's isspace function
+  o fix udev-add.c to build properly with older versions of gcc
+  o add prototype for ftruncate to klibc
+  o Remove a few items from the TODO list that are already done
+  o version number to 005_bk
+  o pull some klibc stuff into the make Makefile to try to stay in sync
+  o klibc build fixes
+
+Kay Sievers:
+  o apply permissions.conf support for wildcard and default name
+  o man page with included placeholder list
+  o implement printf-like placeholder support for NAME
+  o more manpage tweaks
+  o add support for subdirs
+  o add uid/gid to nodes
+
+Olaf Hering:
+  o DESTDIR for udev
+
+Paul Mundt:
+  o Fixup path for kernel includes when building with klibc
+
+Robert Love:
+  o udev init script
+
+
+Summary of changes from v004 to v005
+============================================
+
+<kay:vrfy.org>:
+  o namedev.c comments + debug patch
+  o man page update
+
+Greg Kroah-Hartman:
+  o ignore the klibc/linux symlink
+  o add klibc linux symlink info to the README
+  o get 'make release' to work properly again
+  o added README info for how to build using klibc
+  o turn off debugging if we are building with klibc
+  o turn off debugging in namedev
+  o added vsyslog support to klibc
+  o add ftruncate to klibc
+  o klibc specific tweaks
+  o libsysfs does not need mntent.h in it's header file
+  o udev build tweaks to tdb's spinlock code
+  o klibc makefile changes
+  o build tdb and libsysfs from the same makefile as udev
+  o udev-add build cleanups for other libc versions
+  o tweak tdb to build within udev better
+  o make libsysfs spit debug messages to the same place as the rest of udev
+  o make libsysfs build cleanly
+  o updated bk ignore list
+  o added klibc version 0.82 (cvs tree) to the udev tree
+  o makefile fix for now
+  o Merge greg@bucket:/home/greg/src/udev into kroah.com:/home/greg/src/udev
+  o hm, makefile bug with so many files...  will fix later
+  o regression tests starting to be added
+  o fix LABEL bug for device files (not class files.)
+  o more warning flags to the build
+  o got rid of struct device_attr
+  o rename namedev.permissions and namedev.config to udev.permissions and udev.config
+  o fix dbg line in namedev.c
+  o more overrides of config info with env variables if in test mode
+  o Fix bug causing udev to sleep forever waiting for dev file to show up
+  o change version to 004_bk
+  o make config files, sysfs root, and udev root configurable from config variables
+
+Robert Love:
+  o udev: sleep_for_dev() bits
+  o udev: another canidate for static
+
+
+Summary of changes from v003 to v004
+============================================
+
+Daniel E. F. Stekloff:
+  o new version of libsysfs patch
+
+Greg Kroah-Hartman:
+  o 004 release
+  o major database cleanups
+  o Changed test.block and test.tty to take ACTION from the command line
+  o don't sleep if 'dev' file is already present on device add
+  o fix comment about how the "dev" file is made up
+  o more database work.  Now we only store the info we really need right now
+  o add BUS= bug to TODO list so it will not get forgotten
+  o spec file changes
+  o test.block changes
+  o ok, rpm likes the "_" character instead of "-" better
+  o change the version to 003-bk to keep things sane with people using the bk tree
+  o got "remove of named devices" working
+  o fix segfaults when dealing with partitions
+
+Kay Sievers:
+  o man file update
+  o man page update
+
+Robert Love:
+  o udev: mode should be mode_t
+  o udev: trivial trivialities
+  o udev: cool test scripts again
+  o udev spec file symlink support
+  o udev: cool test scripts
+  o udev spec file bits
+
+
+Summary of changes from v0.2 to v003
+============================================
+
+Daniel E. F. Stekloff:
+  o udevdb patch
+  o udevdb prototype
+
+Greg Kroah-Hartman:
+  o update the spec file for the new version and install process
+  o fix makefile release rule to not drop tdb.h file
+  o Add FAQ for udev
+  o removed AUTHORS and INSTALL files as they were pretty pointless
+  o copyright updates
+  o Add AUTHORS and INSTALL files
+  o TODO updates
+  o Updatd the README
+  o updated the TODO list
+  o add udev man page (basically just a place holder for now.)
+  o added uninstall support
+  o added install target for makefile so people don't have to do it by hand anymore
+  o add version to debug log on startup
+  o tell the user what mknod() we are trying to do
+  o add dbg_parse() to cut down on parse file debugging statements
+  o put config files and database in /etc/udev by default
+  o add ols 2003 udev paper to docs/
+  o clean up some debugging stuff in namedev.c
+  o do not build the tdb binary programs, only the objects
+  o merge tdb into the build process
+  o Added tdb code from latest cvs version in the samba tree
+  o added my name to the .spec file
+  o minor cleanups
+  o cleanup the mknod code a bit
+  o remove mknod callout
+  o handle new major:minor format of dev files that showed up in 2.6.0-test2-bk3 or so
+  o oops, everything was getting created as 000 mode, try to fix this up, but fail...
+  o more test stuff
+
+Olaf Hering:
+  o print udev pid
+
+Patrick Mansfield:
+  o add callout config type to udev
+
+Paul Mundt:
+  o Fix TDB cross compilation
+  o udev spec file
+  o udev/libsysfs cross compile fixes
+
+
+Summary of changes from v0.1 to v0.2
+============================================
+
+Greg Kroah-Hartman:
+  o more test stuff
+  o removed unneeded stuff from udev.h
+  o added 0.2 change log info
+  o start working on label support, and fix some segfaults for block devices
+  o test config file changes
+  o add NUMBER support (basically same logic as TOPOLOGY, perhaps we should
+    merge this...)
+  o added topology support
+  o got REPLACE to work properly
+  o make struct config_device contain a struct device_attr instead of
+    duplicating the mess
+  o block test
+  o split the tests up into different files
+  o split udev main logic into udev-add and udev-remove
+  o Clean up the namedev interface a bit, making the code smaller
+  o bk: update ignore list
+  o update the tests to handle block devices too
+  o add initial libsysfs support
+  o added libsysfs to the build
+  o added libsysfs code from sysutils-0.1.1-071803 release
+  o namedev config files are fully parsed
+  o more permission tests
+  o make log_message spit out warnings so I don't have to spend forever
+    chasing down stupid bugs that aren't there...
+  o added klibc makefile
+  o Initial namedev parsing of config files
+  o sleep for 2 seconds to give the kernel a chance to actually create the
+    files we need
+  o pick a better default UDEV_ROOT
+  o fix up the test to actually work
+  o added more documentation in README and TODO files
+
+
+Summary of changes up to v0.1
+============================================
+
+Greg Kroah-Hartman:
+  o added more documentation in README and TODO files
+  o updated the documentation
+  o cleaned up the makefile a bit
+  o remove now works!
+  o restructure code to be able to actually get remove_node() to work
+  o Creating nodes actually works
+  o added stupid test script for debugging
+  o added initial documentation and gpl license
+  o enabled debugging
+  o updated ignore list
+  o added initial files
+  o fixed up config
+  o Initial repository create
+  o BitKeeper file /home/greg/src/udev/udev/ChangeSet
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0a34e77
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,44 @@
+The options used usually look like:
+  %configure \
+    --prefix=/usr \
+    --sysconfdir=/etc \
+    --bindir=/usr/bin \
+    --libdir=/usr/lib64 \
+    --libexecdir=/usr/lib \
+    --with-systemdsystemunitdir=/usr/lib/systemd/system \
+    --with-selinux
+
+The options used in a RPM spec file look like:
+  %configure \
+    --prefix=%{_prefix} \
+    --sysconfdir=%{_sysconfdir} \
+    --bindir=%{_bindir} \
+    --libdir=%{_libdir} \
+    --libexecdir=%{_prefix}/lib \
+    --with-systemdsystemunitdir=%{_prefix}/lib/systemd/system \
+    --with-selinux
+
+The options to install udev in the rootfs instead of /usr,
+and udevadm in /sbin:
+    --prefix=%{_prefix} \
+    --with-rootprefix= \
+    --sysconfdir=%{_sysconfdir} \
+    --bindir=/sbin \
+    --libdir=%{_libdir} \
+    --with-rootlibdir=/lib64 \
+    --libexecdir=/lib \
+    --with-systemdsystemunitdir=/lib/systemd/system \
+    --with-selinux
+
+Some tools expect udevadm in 'sbin'. A symlink to udevadm in 'bin'
+needs to be manually created if needed.
+
+The defined location for scripts and binaries which are called
+from rules is (/usr)/lib/udev/ on all systems and architectures. Any
+other location will break other packages, who rightfully expect
+the (/usr)/lib/udev/ directory, to install their rule helper and udev
+rule files.
+
+Default udev rules and persistent device naming rules may be required
+by other software that depends on the data udev collects from the
+devices.
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..1c7f86b
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,712 @@
+# Copyright (C) 2008-2012 Kay Sievers <kay.sievers@vrfy.org>
+# Copyright (C) 2009 Diego Elio 'Flameeyes' Pettenò <flameeyes@gmail.com>
+
+SUBDIRS = .
+
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+
+AM_MAKEFLAGS = --no-print-directory
+
+LIBUDEV_CURRENT=13
+LIBUDEV_REVISION=2
+LIBUDEV_AGE=13
+
+LIBGUDEV_CURRENT=1
+LIBGUDEV_REVISION=1
+LIBGUDEV_AGE=1
+
+AM_CPPFLAGS = \
+	-include $(top_builddir)/config.h \
+	-I$(top_srcdir)/src \
+	-DSYSCONFDIR=\""$(sysconfdir)"\" \
+	-DPKGLIBEXECDIR=\""$(libexecdir)/udev"\"
+
+AM_CFLAGS = \
+	${my_CFLAGS} \
+	-fvisibility=hidden \
+	-ffunction-sections \
+	-fdata-sections
+
+AM_LDFLAGS = \
+	-Wl,--gc-sections \
+	-Wl,--as-needed
+
+DISTCHECK_CONFIGURE_FLAGS = \
+	--enable-debug \
+	--enable-rule_generator \
+	--enable-floppy \
+	--with-selinux \
+	--enable-gtk-doc \
+	--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+BUILT_SOURCES =
+EXTRA_DIST =
+CLEANFILES =
+INSTALL_EXEC_HOOKS =
+INSTALL_DATA_HOOKS =
+UNINSTALL_EXEC_HOOKS =
+DISTCHECK_HOOKS =
+DISTCLEAN_LOCAL_HOOKS =
+
+udevhomedir = $(libexecdir)/udev
+udevhome_SCRIPTS =
+dist_udevhome_SCRIPTS =
+dist_udevhome_DATA =
+dist_man_MANS =
+
+SED_PROCESS = \
+	$(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \
+	-e 's,@VERSION\@,$(VERSION),g' \
+	-e 's,@prefix\@,$(prefix),g' \
+	-e 's,@rootprefix\@,$(rootprefix),g' \
+	-e 's,@exec_prefix\@,$(exec_prefix),g' \
+	-e 's,@libdir\@,$(libdir),g' \
+	-e 's,@includedir\@,$(includedir),g' \
+	-e 's,@bindir\@,$(bindir),g' \
+	-e 's,@pkglibexecdir\@,$(libexecdir)/udev,g' \
+	< $< > $@ || rm $@
+
+%.pc: %.pc.in Makefile
+	$(SED_PROCESS)
+
+%.rules: %.rules.in Makefile
+	$(SED_PROCESS)
+
+%.service: %.service.in Makefile
+	$(SED_PROCESS)
+
+%.sh: %.sh.in Makefile
+	$(SED_PROCESS)
+	$(AM_V_GEN)chmod +x $@
+
+%.pl: %.pl.in Makefile
+	$(SED_PROCESS)
+	$(AM_V_GEN)chmod +x $@
+
+# ------------------------------------------------------------------------------
+SUBDIRS += src/docs
+
+include_HEADERS = src/libudev.h
+lib_LTLIBRARIES = libudev.la
+noinst_LTLIBRARIES = libudev-private.la
+
+libudev_la_SOURCES =\
+	src/libudev-private.h \
+	src/libudev.c \
+	src/libudev-list.c \
+	src/libudev-util.c \
+	src/libudev-device.c \
+	src/libudev-enumerate.c \
+	src/libudev-monitor.c \
+	src/libudev-queue.c
+
+libudev_la_LDFLAGS = \
+	$(AM_LDFLAGS) \
+	-version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE)
+
+libudev_private_la_SOURCES =\
+	$(libudev_la_SOURCES) \
+	src/libudev-util-private.c \
+	src/libudev-device-private.c \
+	src/libudev-queue-private.c
+
+if WITH_SELINUX
+libudev_private_la_SOURCES += src/libudev-selinux-private.c
+libudev_private_la_LIBADD = $(SELINUX_LIBS)
+endif
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = src/libudev.pc
+EXTRA_DIST += src/libudev.pc.in
+CLEANFILES += src/libudev.pc
+
+EXTRA_DIST += src/COPYING
+# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed
+libudev-install-move-hook:
+	if test "$(libdir)" != "$(rootlib_execdir)"; then \
+		mkdir -p $(DESTDIR)$(rootlib_execdir) && \
+		so_img_name=$$(readlink $(DESTDIR)$(libdir)/libudev.so) && \
+		so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
+		ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libudev.so && \
+		mv $(DESTDIR)$(libdir)/libudev.so.* $(DESTDIR)$(rootlib_execdir); \
+	fi
+
+libudev-uninstall-move-hook:
+	rm -f $(DESTDIR)$(rootlib_execdir)/libudev.so*
+
+INSTALL_EXEC_HOOKS += libudev-install-move-hook
+UNINSTALL_EXEC_HOOKS += libudev-uninstall-move-hook
+
+# ------------------------------------------------------------------------------
+udev-confdirs:
+	-mkdir -p $(DESTDIR)$(sysconfdir)/udev/rules.d
+	-mkdir -p $(DESTDIR)$(libexecdir)/udev/devices
+
+INSTALL_DATA_HOOKS += udev-confdirs
+
+udevrulesdir = $(libexecdir)/udev/rules.d
+dist_udevrules_DATA = \
+	rules/42-usb-hid-pm.rules \
+	rules/50-udev-default.rules \
+	rules/60-persistent-storage-tape.rules \
+	rules/60-persistent-serial.rules \
+	rules/60-persistent-input.rules \
+	rules/60-persistent-alsa.rules \
+	rules/60-persistent-storage.rules \
+	rules/75-net-description.rules \
+	rules/75-tty-description.rules \
+	rules/78-sound-card.rules \
+	rules/80-drivers.rules \
+	rules/95-udev-late.rules
+
+udevconfdir = $(sysconfdir)/udev
+dist_udevconf_DATA = src/udev.conf
+
+sharepkgconfigdir = $(datadir)/pkgconfig
+sharepkgconfig_DATA = src/udev.pc
+EXTRA_DIST += src/udev.pc.in
+CLEANFILES += src/udev.pc
+
+if WITH_SYSTEMD
+dist_systemdsystemunit_DATA = \
+	src/udev-control.socket \
+	src/udev-kernel.socket
+
+systemdsystemunit_DATA = \
+	src/udev.service \
+	src/udev-trigger.service \
+	src/udev-settle.service
+
+EXTRA_DIST += \
+	src/udev.service.in \
+	src/udev-trigger.service.in \
+	src/udev-settle.service.in
+
+CLEANFILES += \
+	src/udev.service \
+	src/udev-trigger.service \
+	src/udev-settle.service
+
+systemd-install-hook:
+	mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants
+	ln -sf ../udev-control.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-control.socket
+	ln -sf ../udev-kernel.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-kernel.socket
+	mkdir -p $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants
+	ln -sf ../udev.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev.service
+	ln -sf ../udev-trigger.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev-trigger.service
+
+INSTALL_DATA_HOOKS += systemd-install-hook
+endif
+
+bin_PROGRAMS = \
+	udevadm
+
+pkglibexec_PROGRAMS = \
+	udevd
+
+udev_common_sources = \
+	src/udev.h \
+	src/udev-event.c \
+	src/udev-watch.c \
+	src/udev-node.c \
+	src/udev-rules.c \
+	src/udev-ctrl.c \
+	src/udev-builtin.c \
+	src/udev-builtin-blkid.c \
+	src/udev-builtin-firmware.c \
+	src/udev-builtin-hwdb.c \
+	src/udev-builtin-input_id.c \
+	src/udev-builtin-kmod.c \
+	src/udev-builtin-path_id.c \
+	src/udev-builtin-usb_id.c
+
+udev_common_CFLAGS = \
+	$(BLKID_CFLAGS) \
+	$(KMOD_CFLAGS)
+
+udev_common_LDADD = \
+	libudev-private.la \
+	$(BLKID_LIBS) \
+	$(KMOD_LIBS)
+
+udev_common_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-DFIRMWARE_PATH="$(FIRMWARE_PATH)" \
+	-DUSB_DATABASE=\"$(USB_DATABASE)\" -DPCI_DATABASE=\"$(PCI_DATABASE)\"
+
+udevd_SOURCES = \
+	$(udev_common_sources) \
+	src/udevd.c \
+	src/sd-daemon.h \
+	src/sd-daemon.c
+udevd_CFLAGS = $(udev_common_CFLAGS)
+udevd_LDADD = $(udev_common_LDADD)
+udevd_CPPFLAGS = $(udev_common_CPPFLAGS)
+
+udevadm_SOURCES = \
+	$(udev_common_sources) \
+	src/udevadm.c \
+	src/udevadm-info.c \
+	src/udevadm-control.c \
+	src/udevadm-monitor.c \
+	src/udevadm-settle.c \
+	src/udevadm-trigger.c \
+	src/udevadm-test.c \
+	src/udevadm-test-builtin.c
+udevadm_CFLAGS = $(udev_common_CFLAGS)
+udevadm_LDADD = $(udev_common_LDADD)
+udevadm_CPPFLAGS = $(udev_common_CPPFLAGS)
+
+# ------------------------------------------------------------------------------
+if ENABLE_MANPAGES
+dist_man_MANS += \
+	src/udev.7 \
+	src/udevadm.8 \
+	src/udevd.8
+endif
+
+EXTRA_DIST += \
+	src/udev.xml \
+	src/udevadm.xml \
+	src/udevd.xml
+
+if HAVE_XSLTPROC
+dist_noinst_DATA = \
+	src/udev.html \
+	src/udevadm.html \
+	src/udevd.html
+
+src/%.7 src/%.8 : src/%.xml
+	$(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+
+src/%.html : src/%.xml
+	$(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/xhtml-1_1/docbook.xsl $<
+endif
+
+# ------------------------------------------------------------------------------
+TESTS = \
+	test/udev-test.pl \
+	test/rules-test.sh
+
+check_PROGRAMS = \
+	test-libudev \
+	test-udev
+
+test_libudev_SOURCES = src/test-libudev.c
+test_libudev_LDADD = libudev.la
+
+test_udev_SOURCES = \
+	$(udev_common_sources) \
+	src/test-udev.c
+test_udev_CFLAGS = $(udev_common_CFLAGS)
+test_udev_LDADD = $(udev_common_LDADD)
+test_udev_CPPFLAGS = $(udev_common_CPPFLAGS)
+test_udev_DEPENDENCIES = test/sys
+
+# packed sysfs test tree
+test/sys:
+	$(AM_V_GEN)mkdir -p test && tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz
+
+test-sys-distclean:
+	-rm -rf test/sys
+DISTCLEAN_LOCAL_HOOKS += test-sys-distclean
+
+EXTRA_DIST += test/sys.tar.xz
+
+# ------------------------------------------------------------------------------
+ata_id_SOURCES = src/ata_id/ata_id.c
+ata_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += ata_id
+
+# ------------------------------------------------------------------------------
+cdrom_id_SOURCES = src/cdrom_id/cdrom_id.c
+cdrom_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += cdrom_id
+dist_udevrules_DATA += src/cdrom_id/60-cdrom_id.rules
+
+# ------------------------------------------------------------------------------
+collect_SOURCES = src/collect/collect.c
+collect_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += collect
+
+# ------------------------------------------------------------------------------
+scsi_id_SOURCES =\
+	src/scsi_id/scsi_id.c \
+	src/scsi_id/scsi_serial.c \
+	src/scsi_id/scsi.h \
+	src/scsi_id/scsi_id.h
+scsi_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += scsi_id
+dist_man_MANS += src/scsi_id/scsi_id.8
+EXTRA_DIST += src/scsi_id/README
+
+# ------------------------------------------------------------------------------
+v4l_id_SOURCES = src/v4l_id/v4l_id.c
+v4l_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += v4l_id
+dist_udevrules_DATA += src/v4l_id/60-persistent-v4l.rules
+
+# ------------------------------------------------------------------------------
+accelerometer_SOURCES = src/accelerometer/accelerometer.c
+accelerometer_LDADD = libudev-private.la -lm
+pkglibexec_PROGRAMS += accelerometer
+dist_udevrules_DATA += src/accelerometer/61-accelerometer.rules
+
+# ------------------------------------------------------------------------------
+if ENABLE_GUDEV
+SUBDIRS += src/gudev/docs
+
+libgudev_includedir=$(includedir)/gudev-1.0/gudev
+libgudev_include_HEADERS = \
+	src/gudev/gudev.h \
+	src/gudev/gudevenums.h \
+	src/gudev/gudevenumtypes.h \
+	src/gudev/gudevtypes.h \
+	src/gudev/gudevclient.h \
+	src/gudev/gudevdevice.h \
+	src/gudev/gudevenumerator.h
+
+lib_LTLIBRARIES += libgudev-1.0.la
+
+pkgconfig_DATA += src/gudev/gudev-1.0.pc
+EXTRA_DIST += src/gudev/gudev-1.0.pc.in
+CLEANFILES += src/gudev/gudev-1.0.pc
+
+libgudev_1_0_la_SOURCES = \
+	src/gudev/gudevenums.h \
+	src/gudev/gudevenumtypes.h \
+	src/gudev/gudevenumtypes.h\
+	src/gudev/gudevtypes.h \
+	src/gudev/gudevclient.h \
+	src/gudev/gudevclient.c \
+	src/gudev/gudevdevice.h \
+	src/gudev/gudevdevice.c \
+	src/gudev/gudevenumerator.h \
+	src/gudev/gudevenumerator.c \
+	src/gudev/gudevprivate.h
+
+nodist_libgudev_1_0_la_SOURCES = \
+	src/gudev/gudevmarshal.h \
+	src/gudev/gudevmarshal.c \
+	src/gudev/gudevenumtypes.h \
+	src/gudev/gudevenumtypes.c
+BUILT_SOURCES += $(nodist_libgudev_1_0_la_SOURCES)
+
+libgudev_1_0_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_builddir)/src\
+	-I$(top_srcdir)/src\
+	-I$(top_builddir)/src/gudev \
+	-I$(top_srcdir)/src/gudev \
+	-D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT \
+	-D_GUDEV_COMPILATION \
+	-DG_LOG_DOMAIN=\"GUdev\"
+
+libgudev_1_0_la_CFLAGS = \
+	-fvisibility=default \
+	$(GLIB_CFLAGS)
+
+libgudev_1_0_la_LIBADD = libudev.la $(GLIB_LIBS)
+
+libgudev_1_0_la_LDFLAGS = \
+	-version-info $(LIBGUDEV_CURRENT):$(LIBGUDEV_REVISION):$(LIBGUDEV_AGE) \
+	-export-dynamic -no-undefined \
+	-export-symbols-regex '^g_udev_.*'
+
+EXTRA_DIST += \
+	src/gudev/COPYING \
+	src/gudev/gudevmarshal.list \
+	src/gudev/gudevenumtypes.h.template \
+	src/gudev/gudevenumtypes.c.template \
+	src/gudev/gjs-example.js \
+	src/gudev/seed-example-enum.js \
+	src/gudev/seed-example.js
+
+src/gudev/gudevmarshal.h: src/gudev/gudevmarshal.list
+	$(AM_V_GEN)glib-genmarshal $< --prefix=g_udev_marshal --header > $@
+
+src/gudev/gudevmarshal.c: src/gudev/gudevmarshal.list
+	$(AM_V_GEN)echo "#include \"gudevmarshal.h\"" > $@ && \
+	glib-genmarshal $< --prefix=g_udev_marshal --body >> $@
+
+src/gudev/gudevenumtypes.h: src/gudev/gudevenumtypes.h.template src/gudev/gudevenums.h
+	$(AM_V_GEN)glib-mkenums --template $^ > \
+	    $@.tmp && mv $@.tmp $@
+
+src/gudev/gudevenumtypes.c: src/gudev/gudevenumtypes.c.template src/gudev/gudevenums.h
+	$(AM_V_GEN)glib-mkenums --template $^ > \
+	    $@.tmp && mv $@.tmp $@
+
+if ENABLE_INTROSPECTION
+src/gudev/GUdev-1.0.gir: libgudev-1.0.la $(G_IR_SCANNER)
+	$(AM_V_GEN)$(G_IR_SCANNER) -v \
+		--warn-all \
+		--namespace GUdev \
+		--nsversion=1.0 \
+		--include=GObject-2.0 \
+		--library=gudev-1.0 \
+		--library-path=$(top_builddir)/src \
+		--library-path=$(top_builddir)/src/gudev \
+		--output $@ \
+		--pkg=glib-2.0 \
+		--pkg=gobject-2.0 \
+		--pkg-export=gudev-1.0 \
+		--c-include=gudev/gudev.h \
+		-I$(top_srcdir)/src/\
+		-I$(top_builddir)/src/\
+		-D_GUDEV_COMPILATION \
+		-D_GUDEV_WORK_AROUND_DEV_T_BUG \
+		$(top_srcdir)/src/gudev/gudev.h \
+		$(top_srcdir)/src/gudev/gudevtypes.h \
+		$(top_srcdir)/src/gudev/gudevenums.h \
+		$(or $(wildcard $(top_builddir)/src/gudev/gudevenumtypes.h),$(top_srcdir)/src/gudev/gudevenumtypes.h) \
+		$(top_srcdir)/src/gudev/gudevclient.h \
+		$(top_srcdir)/src/gudev/gudevdevice.h \
+		$(top_srcdir)/src/gudev/gudevenumerator.h \
+		$(top_srcdir)/src/gudev/gudevclient.c \
+		$(top_srcdir)/src/gudev/gudevdevice.c \
+		$(top_srcdir)/src/gudev/gudevenumerator.c
+
+src/gudev/GUdev-1.0.typelib: src/gudev/GUdev-1.0.gir $(G_IR_COMPILER)
+	$(AM_V_GEN)g-ir-compiler $< -o $@
+
+girdir = $(GIRDIR)
+gir_DATA = src/gudev/GUdev-1.0.gir
+
+typelibsdir = $(GIRTYPELIBDIR)
+typelibs_DATA = src/gudev/GUdev-1.0.typelib
+
+CLEANFILES += $(gir_DATA) $(typelibs_DATA)
+endif # ENABLE_INTROSPECTION
+
+# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed
+libgudev-install-move-hook:
+	if test "$(libdir)" != "$(rootlib_execdir)"; then \
+		mkdir -p $(DESTDIR)$(rootlib_execdir) && \
+		so_img_name=$$(readlink $(DESTDIR)$(libdir)/libgudev-1.0.so) && \
+		so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
+		ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libgudev-1.0.so && \
+		mv $(DESTDIR)$(libdir)/libgudev-1.0.so.* $(DESTDIR)$(rootlib_execdir); \
+	fi
+
+libgudev-uninstall-move-hook:
+	rm -f $(DESTDIR)$(rootlib_execdir)/libgudev-1.0.so*
+
+INSTALL_EXEC_HOOKS += libgudev-install-move-hook
+UNINSTALL_EXEC_HOOKS += libgudev-uninstall-move-hook
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_KEYMAP
+keymap_SOURCES = src/keymap/keymap.c
+keymap_CPPFLAGS = $(AM_CPPFLAGS) -I src/keymap
+nodist_keymap_SOURCES = \
+	src/keymap/keys-from-name.h \
+	src/keymap/keys-to-name.h
+BUILT_SOURCES += $(nodist_keymap_SOURCES)
+
+pkglibexec_PROGRAMS += keymap
+dist_doc_DATA = src/keymap/README.keymap.txt
+
+dist_udevrules_DATA += \
+	src/keymap/95-keymap.rules \
+	src/keymap/95-keyboard-force-release.rules
+
+dist_udevhome_SCRIPTS += src/keymap/findkeyboards
+udevhome_SCRIPTS += src/keymap/keyboard-force-release.sh
+
+EXTRA_DIST += \
+	src/keymap/check-keymaps.sh \
+	src/keymap/keyboard-force-release.sh.in
+
+CLEANFILES += \
+	src/keymap/keys.txt \
+	src/keymap/keys-from-name.gperf \
+	src/keymap/keyboard-force-release.sh
+
+udevkeymapdir = $(libexecdir)/udev/keymaps
+dist_udevkeymap_DATA = \
+	src/keymap/keymaps/acer \
+	src/keymap/keymaps/acer-aspire_5720 \
+	src/keymap/keymaps/acer-aspire_8930 \
+	src/keymap/keymaps/acer-aspire_5920g \
+	src/keymap/keymaps/acer-aspire_6920 \
+	src/keymap/keymaps/acer-travelmate_c300 \
+	src/keymap/keymaps/asus \
+	src/keymap/keymaps/compaq-e_evo \
+	src/keymap/keymaps/dell \
+	src/keymap/keymaps/dell-latitude-xt2 \
+	src/keymap/keymaps/everex-xt5000 \
+	src/keymap/keymaps/fujitsu-amilo_li_2732 \
+	src/keymap/keymaps/fujitsu-amilo_pa_2548 \
+	src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 \
+	src/keymap/keymaps/fujitsu-amilo_pro_v3205 \
+	src/keymap/keymaps/fujitsu-amilo_si_1520 \
+	src/keymap/keymaps/fujitsu-esprimo_mobile_v5 \
+	src/keymap/keymaps/fujitsu-esprimo_mobile_v6 \
+	src/keymap/keymaps/genius-slimstar-320 \
+	src/keymap/keymaps/hewlett-packard \
+	src/keymap/keymaps/hewlett-packard-2510p_2530p \
+	src/keymap/keymaps/hewlett-packard-compaq_elitebook \
+	src/keymap/keymaps/hewlett-packard-pavilion \
+	src/keymap/keymaps/hewlett-packard-presario-2100 \
+	src/keymap/keymaps/hewlett-packard-tablet \
+	src/keymap/keymaps/hewlett-packard-tx2 \
+	src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint \
+	src/keymap/keymaps/inventec-symphony_6.0_7.0 \
+	src/keymap/keymaps/lenovo-3000 \
+	src/keymap/keymaps/lenovo-ideapad \
+	src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint \
+	src/keymap/keymaps/lenovo-thinkpad_x6_tablet \
+	src/keymap/keymaps/lenovo-thinkpad_x200_tablet \
+	src/keymap/keymaps/lg-x110 \
+	src/keymap/keymaps/logitech-wave \
+	src/keymap/keymaps/logitech-wave-cordless \
+	src/keymap/keymaps/logitech-wave-pro-cordless \
+	src/keymap/keymaps/maxdata-pro_7000 \
+	src/keymap/keymaps/medion-fid2060 \
+	src/keymap/keymaps/medionnb-a555 \
+	src/keymap/keymaps/micro-star \
+	src/keymap/keymaps/module-asus-w3j \
+	src/keymap/keymaps/module-ibm \
+	src/keymap/keymaps/module-lenovo \
+	src/keymap/keymaps/module-sony \
+	src/keymap/keymaps/module-sony-old \
+	src/keymap/keymaps/module-sony-vgn \
+	src/keymap/keymaps/olpc-xo \
+	src/keymap/keymaps/onkyo \
+	src/keymap/keymaps/oqo-model2 \
+	src/keymap/keymaps/samsung-other \
+	src/keymap/keymaps/samsung-90x3a \
+	src/keymap/keymaps/samsung-sq1us \
+	src/keymap/keymaps/samsung-sx20s \
+	src/keymap/keymaps/toshiba-satellite_a100 \
+	src/keymap/keymaps/toshiba-satellite_a110 \
+	src/keymap/keymaps/toshiba-satellite_m30x \
+	src/keymap/keymaps/zepto-znote
+
+udevkeymapforcereldir = $(libexecdir)/udev/keymaps/force-release
+dist_udevkeymapforcerel_DATA = \
+	src/keymap/force-release-maps/dell-touchpad \
+	src/keymap/force-release-maps/hp-other \
+	src/keymap/force-release-maps/samsung-other \
+	src/keymap/force-release-maps/samsung-90x3a \
+	src/keymap/force-release-maps/common-volume-keys
+
+src/keymap/keys.txt: $(INCLUDE_PREFIX)/linux/input.h
+	$(AM_V_at)mkdir -p src/keymap
+	$(AM_V_GEN)$(AWK) '/^#define.*KEY_[^ ]+[ \t]+[0-9]/ { if ($$2 != "KEY_MAX") { print $$2 } }' < $< | sed 's/^KEY_COFFEE$$/KEY_SCREENLOCK/' > $@
+
+src/keymap/keys-from-name.gperf: src/keymap/keys.txt
+	$(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print $$1 ", " $$1 }' < $< > $@
+
+src/keymap/keys-from-name.h: src/keymap/keys-from-name.gperf Makefile
+	$(AM_V_GEN)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_key -H hash_key_name -p -C < $< > $@
+
+src/keymap/keys-to-name.h: src/keymap/keys.txt Makefile
+	$(AM_V_GEN)$(AWK) 'BEGIN{ print "const char* const key_names[KEY_CNT] = { "} { print "[" $$1 "] = \"" $$1 "\"," } END{print "};"}' < $< > $@
+
+keymaps-distcheck-hook: src/keymap/keys.txt
+	$(top_srcdir)/src/keymap/check-keymaps.sh $(top_srcdir) $^
+DISTCHECK_HOOKS += keymaps-distcheck-hook
+endif
+
+if ENABLE_MTD_PROBE
+# ------------------------------------------------------------------------------
+mtd_probe_SOURCES =  \
+	src/mtd_probe/mtd_probe.c \
+	src/mtd_probe/mtd_probe.h \
+	src/mtd_probe/probe_smartmedia.c
+mtd_probe_CPPFLAGS = $(AM_CPPFLAGS)
+dist_udevrules_DATA += src/mtd_probe/75-probe_mtd.rules
+pkglibexec_PROGRAMS += mtd_probe
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_RULE_GENERATOR
+dist_udevhome_SCRIPTS += \
+	src/rule_generator/write_cd_rules \
+	src/rule_generator/write_net_rules
+
+dist_udevhome_DATA += \
+	src/rule_generator/rule_generator.functions
+
+dist_udevrules_DATA += \
+	src/rule_generator/75-cd-aliases-generator.rules \
+	src/rule_generator/75-persistent-net-generator.rules
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_FLOPPY
+create_floppy_devices_SOURCES = src/floppy/create_floppy_devices.c
+create_floppy_devices_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += create_floppy_devices
+dist_udevrules_DATA += src/floppy/60-floppy.rules
+endif
+
+# ------------------------------------------------------------------------------
+clean-local:
+	rm -rf udev-test-install
+
+distclean-local:
+	rm -rf autom4te.cache
+
+EXTRA_DIST += \
+	$(TESTS) \
+	test/rule-syntax-check.py
+
+CLEANFILES += \
+	$(BUILT_SOURCES)
+
+install-exec-hook: $(INSTALL_EXEC_HOOKS)
+
+install-data-hook: $(INSTALL_DATA_HOOKS)
+
+uninstall-hook: $(UNINSTALL_EXEC_HOOKS)
+
+distcheck-hook: $(DISTCHECK_HOOKS)
+
+distclean-local: $(DISTCLEAN_LOCAL_HOOKS)
+
+# ------------------------------------------------------------------------------
+PREVIOUS_VERSION = `expr $(VERSION) - 1`
+changelog:
+	@ head -1 ChangeLog | grep -q "to v$(PREVIOUS_VERSION)"
+	@ mv ChangeLog ChangeLog.tmp
+	@ echo "Summary of changes from v$(PREVIOUS_VERSION) to v$(VERSION)" >> ChangeLog
+	@ echo "============================================" >> ChangeLog
+	@ echo >> ChangeLog
+	@ git log --pretty=short $(PREVIOUS_VERSION)..HEAD | git shortlog  >> ChangeLog
+	@ echo >> ChangeLog
+	@ cat ChangeLog
+	@ cat ChangeLog.tmp >> ChangeLog
+	@ rm ChangeLog.tmp
+
+test-install:
+	rm -rf $(PWD)/udev-test-install/
+	make DESTDIR=$(PWD)/udev-test-install install
+	tree $(PWD)/udev-test-install/
+
+git-release:
+	head -1 ChangeLog | grep -q "to v$(VERSION)"
+	head -1 NEWS | grep -q "udev $(VERSION)"
+	git commit -a -m "release $(VERSION)"
+	git tag -m "udev $(VERSION)" -s $(VERSION)
+	git gc --prune=0
+
+git-sync:
+	git push
+	git push --tags
+
+tar-sync:
+	rm -f udev-$(VERSION).tar.sign
+	xz -d -c udev-$(VERSION).tar.xz | gpg --armor --detach-sign --output udev-$(VERSION).tar.sign
+	kup put udev-$(VERSION).tar.xz  udev-$(VERSION).tar.sign /pub/linux/utils/kernel/hotplug/
+
+doc-sync:
+	for i in src/*.html; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
+	for i in src/*.html; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/udev/; done
+	for i in src/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
+	for i in src/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/libudev/; done
+	for i in src/gudev/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
+	for i in src/gudev/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/gudev/; done
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..f4f6f4e
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,1735 @@
+udev 182
+========
+Rules files in /etc/udev/rules.s/ with the same name as rules files in
+/run/udev/rules.d/ now always have precedence. The stack of files is now:
+/usr/lib (package), /run (runtime, auto-generated), /etc (admin), while
+the later ones override the earlier ones. In other words: the admin has
+always the last say.
+
+USB auto-suspend is now enabled by default for some built-in USB HID
+devices.
+
+/dev/disk/by-path/ links are no longer created for ATA devices behind
+an 'ATA transport class', the logic to extract predictable numbers does
+not exist in the kernel at this moment.
+
+/dev/disk/by-id/scsi-* compatibility links are no longer created for
+ATA devices, they have their own ata-* prefix.
+
+The s390 rule to set mode == 0666 for /dev/z90crypt is is removed from
+the udev tree and will be part of s390utils (or alternatively could be
+done by the kernel driver itself).
+
+The udev-acl tool is no longer provided, it will be part of a future
+ConsoleKit release. On systemd systems, advanced ConsoleKit and udev-acl
+functionality are provided by systemd.
+
+udev 181
+========
+Require kmod version 5.
+
+Provide /dev/cdrom symlink for /dev/sr0.
+
+udev 180
+========
+Fix for ID_PART_ENTRY_* property names, added by the blkid built-in. The
+fix is needed for udisk2 to operate properly.
+
+Fix for skipped rule execution when the kernel has removed the device
+node in /dev again, before the event was even started. The fix is needed
+to run device-mapper/LVM events properly.
+
+Fix for the man page installation, which was skipped when xsltproc was not
+installed.
+
+udev 179
+========
+Bugfix for $name resolution, which broke at least some keymap handling.
+
+udev 178
+========
+Bugfix for the firmware loading behavior with kernel modules which
+try to load firmware in the module_init() path. The blocked event
+runs into a timout now, which should allow the firmware to be loaded.
+
+Bugfix for a wrong DEVNAME= export, which breaks at least the udev-acl
+tool.
+
+Bugfix for missing ID_ properties for GPT partitions.
+
+The RUN+="socket:.." option is deprecated and should not be used. A warning
+during rules parsing is printed now. Services which listen to udev events,
+need to subscribe to the netlink messages with libudev and not let udev block
+in the rules execution until the message is delivered.
+
+udev 177
+========
+Bugfix for rule_generator instalation.
+
+udev 176
+========
+The 'devtmpfs' filesystem is required now, udev will not create or delete
+device nodes anymore, it only adjusts permissions and ownership of device
+nodes and maintains additional symlinks.
+
+A writable /run directory (ususally tmpfs) is required now for a fully
+functional udev, there is no longer a fallback to /dev/.udev.
+
+The default 'configure' install locations have changed. Packages for systems
+with the historic / vs. /usr split need to be adapted, otherwise udev will
+be installed in /usr and not work properly. Example configuration options
+to install things the traditional way are in INSTALL.
+
+The default install location of the 'udevadm' tool moved from 'sbin'
+to /usr/bin. Some tools expect udevadm in 'sbin', a symlink to udevadm
+needs to be manually created if needed, or --bindir=/sbin be specified.
+
+The expected value of '--libexecdir=' has changed and must no longer contain
+the 'udev' directory.
+
+Kernel modules are now loaded directly by linking udev to 'libkmod'. The
+'modprobe' tool is no longer executed by udev.
+
+The 'blkid' tool is no longer executed from udev rules. Udev links
+directly to libblkid now.
+
+Firmware is loaded natively by udev now, the external 'firmware' binary
+is no longer used.
+
+All built-in tools can be listed and tested with 'udevadm test-builtin'.
+
+The 'udevadm control --reload-rules' option has been renamed to '--reload'.
+It now also reloads the kernel module configuration.
+
+The systemd socket files use PassCredentials=yes, which is available in
+systemd version 38.
+
+The udev build system only creates a .xz tarball now.
+
+All tabs in the source code used for indentation are replaced by spaces now. :)
+
+udev 175
+========
+Bugfixes.
+
+udev 174
+========
+Bugfixes.
+
+The udev daemon moved to /lib/udev/udevd. Non-systemd init systems
+and non-dracut initramfs image generators need to change the init
+scripts. Alternatively the udev build needs to move udevd back to
+/sbin or create a symlink in /sbin, which is not done by default.
+
+The path_id, usb_id, input_id tools are built-in commands now and
+the stand-alone tools do not exist anymore. Static lists of file in
+initramfs generators need to be updated. For testing, the commands
+can still be executed standalone with 'udevadm test-builtin <cmd>'.
+
+The fusectl filesystem is no longer mounted directly from udev.
+Systemd systems will take care of mounting fusectl and configfs
+now. Non-systemd systems need to ship their own rule if they
+need these filesystems auto-mounted.
+
+The long deprecated keys: SYSFS=, ID=, BUS= have been removed.
+
+The support for 'udevadm trigger --type=failed, and the
+RUN{fail_event_on_error} attribute was removed.
+
+The udev control socket is now created in /run/udev/control
+and no longer as an abstract namespace one.
+
+The rules to create persistent network interface and cdrom link
+rules automatically in /etc/udev/rules.d/ have been disabled by
+default. Explicit configuration will be required for these use
+cases, udev will no longer try to write any persistent system
+configuration from a device hotplug path.
+
+udev 173
+========
+Bugfixes.
+
+The udev-acl extra is no longer enabled by default now. To enable it,
+--enable-udev_acl needs to be given at ./configure time. On systemd
+systems, the udev-acl rules prevent it from running as the functionality
+has moved to systemd.
+
+udev 172
+========
+Bugfixes.
+
+Udev now enables kernel media-presence polling if available. Part
+of udisks optical drive tray-handling moved to cdrom_id: The tray
+is locked as soon as a media is detected to enable the receiving
+of media-eject-request events. Media-eject-request events will
+eject the media.
+
+Libudev enumerate is now able to enumerate a subtree of a given
+device.
+
+The mobile-action-modeswitch modeswitch tool was deleted. The
+functionality is provided by usb_modeswitch now.
+
+udev 171
+========
+Bugfixes.
+
+The systemd service files require systemd version 28. The systemd
+socket activation make it possible now to start 'udevd' and 'udevadm
+trigger' in parallel.
+
+udev 170
+========
+Fix bug in control message handling, which can lead to a failing
+udevadm control --exit. Thanks to Jürg Billeter for help tracking
+it down.
+
+udev 169
+========
+Bugfixes.
+
+We require at least Linux kernel 2.6.32 now. Some platforms might
+require a later kernel that supports accept4() and similar, or
+need to backport the trivial syscall wiring to the older kernels.
+
+The hid2hci tool moved to the bluez package and was removed.
+
+Many of the extras can be --enable/--disabled at ./configure
+time. The --disable-extras option was removed. Some extras have
+been disabled by default. The current options and their defaults
+can be checked with './configure --help'.
+
+udev 168
+========
+Bugfixes.
+
+Udev logs a warning now if /run is not writable at udevd
+startup. It will still fall back to /dev/.udev, but this is
+now considered a bug.
+
+The running udev daemon can now cleanly shut down with:
+  udevadm control --exit
+
+Udev in initramfs should clean the state of the udev database
+with: udevadm info --cleanup-db which will remove all state left
+behind from events/rules in initramfs. If initramfs uses
+--cleanup-db and device-mapper/LVM, the rules in initramfs need
+to add OPTIONS+="db_persist" for all dm devices. This will
+prevent removal of the udev database for these devices.
+
+Spawned programs by PROGRAM/IMPORT/RUN now have a hard timeout of
+120 seconds per process. If that timeout is reached the spawned
+process will be killed. The event timeout can be overwritten with
+udev rules.
+
+If systemd is used, udev gets now activated by netlink data.
+Systemd will bind the netlink socket which will buffer all data.
+If needed, such setup allows a seemless update of the udev daemon,
+where no event can be lost during a udevd update/restart.
+Packages need to make sure to: systemctl stop udev.socket udev.service
+or 'mask' udev.service during the upgrade to prevent any unwanted
+auto-spawning of udevd.
+This version of udev conflicts with systemd version below 25. The
+unchanged service files will not wirk correctly.
+
+udev 167
+========
+Bugfixes.
+
+The udev runtime data moved from /dev/.udev/ to /run/udev/. The
+/run mountpoint is supposed to be a tmpfs mounted during early boot,
+available and writable to for all tools at any time during bootup,
+it replaces /var/run/, which should become a symlink some day.
+
+If /run does not exist, or is not writable, udev will fall back using
+/dev/.udev/.
+
+On systemd systems with initramfs and LVM used, packagers must
+make sure, that the systemd and initramfs versions match. The initramfs
+needs to create the /run mountpoint for udev to store the data, and
+mount this tmpfs to /run in the rootfs, so the that the udev database
+is preserved for the udev version started in the rootfs.
+
+The command 'udevadm info --convert-db' is gone. The udev daemon
+itself, at startup, converts any old database version if necessary.
+
+The systemd services files have been reorganized. The udev control
+socket is bound by systemd and passed to the started udev daemon.
+The udev-settle.service is no longer active by default. Services which
+can not handle hotplug setups properly need to actively pull it in, to
+act like a barrier. Alternatively the settle service can be unconditionally
+'systemctl'enabled, and act like a barrier for basic.target.
+
+The fstab_import callout is no longer built or installed. Udev
+should not be used to mount, does not watch changes to fstab, and
+should not mirror fstab values in the udev database.
+
+udev 166
+========
+Bugfixes.
+
+New and updated keymaps.
+
+udev 165
+========
+Bugfixes.
+
+The udev database has changed, After installation of a new udev
+version, 'udevadm info --convert-db' should be called, to let the new
+udev/libudev version read the already stored data.
+
+udevadm now supports quoting of property values, and prefixing of
+key names:
+  $ udevadm info --export --export-prefix=MY_ --query=property -n sda
+  MY_MAJOR='259'
+  MY_MINOR='0'
+  MY_DEVNAME='/dev/sda'
+  MY_DEVTYPE='disk'
+  ...
+
+libudev now supports:
+  udev_device_get_is_initialized()
+  udev_enumerate_add_match_is_initialized()
+to be able to skip devices the kernel has created , but udev has
+not already handled.
+
+libudev now supports:
+  udev_device_get_usec_since_initialized()
+to retrieve the "age" of a udev device record.
+
+GUdev supports a more generic GUdevEnumerator class, udev TAG
+handling, device initialization and timestamp now.
+
+The counterpart of /sys/dev/{char,block}/$major:$minor,
+/dev/{char,block}/$major:$minor symlinks are now unconditionally
+created, even when no rule files exist.
+
+New and updated keymaps.
+
+udev 164
+========
+Bugfixes.
+
+GUdev moved from /usr to /.
+
+udev 163
+========
+Bugfixes.
+
+udev 162
+========
+Bugfixes.
+
+Persistent network naming rules are disabled inside of Qemu/KVM now.
+
+New and updated keymaps.
+
+Udev gets unconditionally enabled on systemd installations now. There
+is no longer the need to to run 'systemctl enable udev.service'.
+
+udev 161
+========
+Bugfixes.
+
+udev 160
+========
+Bugfixes.
+
+udev 159
+========
+Bugfixes.
+
+New and fixed keymaps.
+
+Install systemd service files if applicable.
+
+udev 158
+========
+Bugfixes.
+
+All distribution specific rules are removed from the udev source tree,
+most of them are no longer needed. The Gentoo rules which allow to support
+older kernel versions, which are not covered by the default rules anymore
+has moved to rules/misc/30-kernel-compat.rules.
+
+udev 157
+========
+Bugfixes.
+
+The option --debug-trace and the environemnt variable UDEVD_MAX_CHILDS=
+was removed from udevd.
+
+Udevd now checks the kernel commandline for the following variables:
+  udev.log-priority=<syslog priority>
+  udev.children-max=<maximum number of workers>
+  udev.exec-delay=<seconds to delay the execution of RUN=>
+to help debuging coldplug setups where the loading of a kernel
+module crashes the system.
+
+The subdirectory in the source tree rules/packages has been renamed to
+rules/arch, anc contains only architecture specific rules now.
+
+udev 156
+========
+Bugfixes.
+
+udev 155
+========
+Bugfixes.
+
+Now the udev daemon itself, does on startup:
+  - copy the content of /lib/udev/devices to /dev
+  - create the standard symlinks like /dev/std{in,out,err},
+    /dev/core, /dev/fd, ...
+  - use static node information provided by kernel modules
+    and creates these nodes to allow module on-demand loading
+  - possibly apply permissions to all ststic nodes from udev
+    rules which are annotated to match a static node
+
+The default mode for a device node is 0600 now to match the kernel
+created devtmpfs defaults. If GROUP= is specified and no MODE= is
+given the default will be 0660.
+
+udev 154
+========
+Bugfixes.
+
+Udev now gradually starts to pass control over the primary device nodes
+and their names to the kernel, and will in the end only manage the
+permissions of the node, and possibly create additional symlinks.
+As a first step NAME="" will be ignored, and NAME= setings with names
+other than the kernel provided name will result in a logged warning.
+Kernels that don't provide device names, or devtmpfs is not used, will
+still work as they did before, but it is strongly recommended to use
+only the same names for the primary device node as the recent kernel
+provides for all devices.
+
+udev 153
+========
+Fix broken firmware loader search path.
+
+udev 152
+========
+Bugfixes.
+
+"udevadm trigger" defaults to "change" events now instead of "add"
+events. The "udev boot script" might need to add "--action=add" to
+the trigger command if not already there, in case the initial coldplug
+events are expected as "add" events.
+
+The option "all_partitons" was removed from udev. This should not be
+needed for usual hardware. Udev can not safely make assumptions
+about non-existing partition major/minor numbers, and therefore no
+longer provide this unreliable and unsafe option.
+
+The option "ignore_remove" was removed from udev. With devtmpfs
+udev passed control over device nodes to the kernel. This option
+should not be needed, or can not work as advertised. Neither
+udev nor the kernel will remove device nodes which are copied from
+the /lib/udev/devices/ directory.
+
+All "add|change" matches are replaced by "!remove" in the rules and
+in the udev logic. All types of events will update possible symlinks
+and permissions, only "remove" is handled special now.
+
+The modem modeswitch extra was removed and the external usb_modeswitch
+program should be used instead.
+
+New and fixed keymaps.
+
+udev 151
+========
+Bugfixes.
+
+udev 150
+========
+Bugfixes.
+
+Kernels with SYSFS_DEPRECATED=y are not supported since a while. Many users
+depend on the current sysfs layout and the information not available in the
+deprecated layout. All remaining support for the deprecated sysfs layout is
+removed now.
+
+udev 149
+========
+Fix for a possible endless loop in the new input_id program.
+
+udev 148
+========
+Bugfixes.
+
+The option "ignore_device" does no longer exist. There is no way to
+ignore an event, as libudev events can not be suppressed by rules.
+It only prevented RUN keys from being executed, which results in an
+inconsistent behavior in current setups.
+
+BUS=, SYSFS{}=, ID= are long deprecated and should be SUBSYSTEM(S)=,
+ATTR(S){}=, KERNEL(S)=. It will cause a warning once for every rule
+file from now on.
+
+The support for the deprecated IDE devices has been removed from the
+default set of rules. Distros who still care about non-libata drivers
+need to add the rules to the compat rules file.
+
+The ID_CLASS property on input devices has been replaced by the more accurate
+set of flags ID_INPUT_{KEYBOARD,KEY,MOUSE,TOUCHPAD,TABLET,JOYSTICK}. These are
+determined by the new "input_id" prober now. Some devices, such as touchpads,
+can have several classes. So if you previously had custom udev rules which e. g.
+checked for ENV{ID_CLASS}=="kbd", you need to replace this with
+ENV{ID_INPUT_KEYBOARD}=="?*".
+
+udev 147
+========
+Bugfixes.
+
+To support DEVPATH strings larger than the maximum file name length, the
+private udev database format has changed. If some software still reads the
+private files in /dev/.udev/, which it shouldn't, now it's time to fix it.
+Please do not port anything to the new format again, everything in /dev/.udev
+is and always was private to udev, and may and will change any time without
+prior notice.
+
+Multiple devices claiming the same names in /dev are limited to symlinks
+only now. Mixing identical symlink names and node names is not supported.
+This reduces the amount of data in the database significantly.
+
+NAME="%k" causes a warning now. It's is and always was completely superfluous.
+It will break kernel supplied DEVNAMEs and therefore it needs to be removed
+from all rules.
+
+Most NAME= instructions got removed. Kernel 2.6.31 supplies the needed names
+if they are not the default. To support older kernels, the NAME= rules need to
+be added to the compat rules file.
+
+Symlinks to udevadm with the old command names are no longer resolved to
+the udevadm commands.
+
+The udev-acl tool got adopted to changes in ConsoleKit. Version 0.4.1 is
+required now.
+
+The option "last_rule" does no longer exist. Its use breaks too many
+things which expect to be run from independent later rules, and is an idication
+that something needs to be fixed properly instead.
+
+The gudev API is no longer marked as experimental,
+G_UDEV_API_IS_SUBJECT_TO_CHANGE is no longer needed. The gudev introspection
+is enabled by default now. Various projects already depend on introspection
+information to bind dynamic languages to the gudev interfaces.
+
+udev 146
+========
+Bugfixes.
+
+The udevadm trigger "--retry-failed" option, which is replaced since quite
+a while by "--type=failed" is removed.
+
+The failed tracking was not working at all for a few releases. The RUN
+option "ignore_error" is replaced by a "fail_event_on_error" option, and the
+default is not to track any failing RUN executions.
+
+New keymaps, new modem, hid2hci updated.
+
+udev 145
+========
+Fix possible crash in udevd when worker processes are busy, rules are
+changed at the same time, and workers get killed to reload the rules.
+
+udev 144
+========
+Bugfixes.
+
+Properties set with ENV{.FOO}="bar" are marked private by starting the
+name with a '.'. They will not be stored in the database, and not be
+exported with the event.
+
+Firmware files are looked up in:
+  /lib/firmware/updates/$(uname -r)
+  /lib/firmware/updates
+  /lib/firmware/$(uname -r)
+  /lib/firmware"
+now.
+
+ATA devices switched the property from ID_BUS=scsi to ID_BUS=ata.
+ata_id, instead of scsi_id, is the default tool now for ATA devices.
+
+udev 143
+========
+Bugfixes.
+
+The configure options have changed because another library needs to be
+installed in a different location. Instead of exec_prefix and udev_prefix,
+libdir, rootlibdir and libexecdir are used. The Details are explained in
+the README file.
+
+Event processes now get re-used after they handled an event. This reduces
+the number of forks and the pressure on the CPU significantly, because
+cloned event processes no longer cause page faults in the main daemon.
+After the events have settled, a few worker processes stay around for
+future events, all others get cleaned up.
+
+To be able to use signalfd(), udev depends on kernel version 2.6.25 now.
+Also inotify support is mandatory now to run udev.
+
+The format of the queue exported by the udev damon has changed. There is
+no longer a /dev/.udev/queue/ directory. The current event queue can be
+accessed with udevadm settle and libudedv.
+
+Libudev does not have the unstable API header anymore. From now on,
+incompatible changes will be handled by bumping the library major version.
+
+To build udev from the git tree gtk-doc is needed now. The tarballs will
+build without it and contain the pre-built documentation. An online copy
+is available here:
+  http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
+
+The tools from the udev-extras repository have been merged into the main
+udev repository. Some of the extras have larger external dependencies, and
+they can be disabled with the configure switch --disable-extras.
+
+udev 142
+========
+Bugfixes.
+
+The program vol_id and the library libvolume_id are removed from the
+repository. Libvolume_id is merged with libblkid from the util-linux-ng
+package. Persistent disk links for label and uuid depend on the
+util-linux-ng version (2.15) of blkid now. Older versions of blkid
+can not be used with udev.
+
+Libudev allows to subscribe to udev events. To prevent unwanted messages
+to be delivered, and waking up the subscribing process, a filter can be
+installed, to drop messages inside a kernel socket filter. The filters
+match on the <subsytem>:<devtype> properties of the device.
+    This is part of the ongoing effort to replace HAL, and switch current
+users over to directly use libudev.
+    Libudev is still marked as experimental, and its interface might
+eventually change if needed, but no major changes of the currently exported
+interface are expected anymore, and a first stable release should happen
+soon.
+
+A too old kernel (2.6.21) or a kernel with CONFIG_SYSFS_DEPRECATED
+is not supported since while and udevd will log an error message at
+startup. It should still be able to boot-up, but advanced rules and system
+services which depend on the information not available in the old sysfs
+format will fail to work correctly.
+
+DVB device naming is supplied by the kernel now. In case older kernels
+need to be supported, the old shell script should be added to a compat
+rules file.
+
+udev 141
+========
+Bugfixes.
+
+The processed udev events get send back to the netlink socket. Libudev
+provides access to these events. This is work-in-progress, to replace
+the DeviceKit daemon functionality directly with libudev. There are
+upcoming kernel changes to allow non-root users to subcribe to these
+events.
+
+udev 140
+========
+Bugfixes.
+
+"udevadm settle" now optionally accepts a range of events to wait for,
+instead of waiting for "all" events.
+
+udev 139
+========
+Bugfixes.
+
+The installed watch for block device metadata changes is now removed
+during event hadling, because some (broken) tools may be called from udev
+rules and (wrongly) open the device with write access. After the finished
+event handling the watch is restored.
+
+udev 138
+========
+Bugfixes.
+
+Device nodes can be watched for changes with inotify with OPTIONS="watch".
+If closed after being opened for writing, a "change" uevent will occur.
+/dev/disk/by-{label,uuid}/* symlinks will be automatically updated.
+
+udev 137
+========
+Bugfixes.
+
+The udevadm test command has no longer a --force option, nodes and symlinks
+are always updated with a test run now.
+
+The udevd daemon can be started with --resolve-names=never to avoid all user
+and group lookups (e.g. in cut-down systems) or --resolve-names=late to
+lookup user and groups every time events are handled.
+
+udev 136
+========
+Bugfixes.
+
+We are currently merging the Ubuntu rules in the udev default rules,
+and get one step closer to provide a common Linux /dev setup, regarding
+device names, symlinks, and default device permissions. On udev startup,
+we now expect the following groups to be resolvable to their ids with
+glibc's getgrnam():
+  disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, kmem.
+LDAP setups need to make sure, that these groups are always resolvable at
+bootup, with only the rootfs mounted, and without network access available.
+
+Some systems may need to add some new, currently not used groups, or need
+to add some users to new groups, but the cost of this change is minimal,
+compared to the pain the current, rather random, differences between the
+various distributions cause for upstream projects and third-party vendors.
+
+In general, "normal" users who log into a machine should never be a member
+of any such group, but the device-access should be managed by dynamic ACLs,
+which get added and removed for the specific users on login/logout and
+session activity/inactivity. These groups are only provided for custom setups,
+and mainly system services, to allow proper privilege separation.
+A video-streaming daemon uid would be a member of "audio" and "video", to get
+access to the sound and video devices, but no "normal" user should ever belong
+to the "audio" group, because he could listen to the built-in microphone with
+any ssh-session established from the other side of the world.
+
+/dev/serial/by-{id,path}/ now contains links for ttyUSB devices,
+which do not depend on the kernel device name. As usual, unique
+devices - only a single one per product connected, or a real
+USB serial number in the device - are always found with the same
+name in the by-id/ directory.
+Completely identical devices may overwrite their names in by-id/
+and can only be found reliably in the by-path/ directory. Devices
+specified by by-path/ must not change their connection, like the
+USB port number they are plugged in, to keep their name.
+
+To support some advanced features, Linux 2.6.22 is the oldest supported
+version now. The kernel config with enabled SYSFS_DEPRECATED is no longer
+supported. Older kernels should still work, and devices nodes should be
+reliably created, but some rules and libudev will not work correctly because
+the old kernels do not provide the expected information or interfaces.
+
+udev 135
+========
+Bugfixes.
+
+Fix for a possible segfault while swapping network interface names in udev
+versions 131-134.
+
+udev 134
+========
+Bugfixes.
+
+The group "video" is part of the default rules now.
+
+udev 133
+========
+Bugfix for kernels using SYSFS_DEPRECATED* option and finding parent
+block devices in some cases. No common distro uses this option anymore,
+and we do not get enough testing for this and recent udev versions. If
+this option is not needed to run some old distro with a new kernel,
+it should be disabled in the kernel config.
+
+Bugfix for the $links substitution variable, which may crash if no links
+are created. This should not happen in usual setups because we always
+create /dev/{block,char}/ links.
+
+The strings of the parsed rules, which are kept in memory, no longer
+contain duplicate entries, or duplicate tails of strings. This, and the
+new rules parsing/matching code reduces the total in-memory size of
+a huge distro rule sets to 0.08 MB, compared to the 1.2MB of udev
+version 130.
+
+The export of DEVTYPE=disk/partition got removed from the default
+rules. This value is available from the kernel. The pnp shell script
+modprobe hack is removed from the default rules. ACPI devices have _proper_
+modalias support and take care of the same functionality.
+Installations which support old kernels, but install current default
+udev rules may want to add that to the compat rules file.
+
+Libvolume_id now always probes for all known filesystems, and does not
+stop at the first match. Some filesystems are marked as "exclusive probe",
+and if any other filesytem type matches at the same time, libvolume_id
+will, by default, not return any probing result. This is intended to prevent
+mis-detection with conflicting left-over signatures found from earlier
+file system formats. That way, we no longer depend on the probe-order
+in case of multiple competing signatures. In some setups the kernel allows
+to mount a volume with just the old filesystem signature still in place.
+This may damage the new filesystem and cause data-loss, just by mounting
+it. Because volume_id can not decide which one the correct signature is,
+the wrong signatures need to be removed manually from the volume, or the
+volume needs to be reformatted, to enable filesystem detection and possible
+auto-mounting.
+
+udev 132
+========
+Fix segfault if compiled without optimization and dbg() does not get
+compiled out and uses variables which are not available.
+
+udev 131
+========
+Bugfixes. (And maybe new bugs. :))
+
+The rule matching engine got converted from a rule list to a token
+array which reduced the in-memory rules representation of a full
+featured distros with thousends of udev rules from 1.2MB to 0.12 MB.
+Limits like 5 ENV and ATTR matches, and one single instance for most
+other keys per rule are gone.
+
+The NAME assignment is no longer special cased. If later rules assign
+a NAME value again, the former value will be overwritten. As usual
+for most other keys, the NAME value can be protected by doing a final
+assignment with NAME:="<value>".
+
+All udev code now uses libudev, which is also exported. The library
+is still under development, marked as experimental, and its interface
+may change as long as the DeviceKit integration is not finished.
+
+Many thanks to Alan Jenkins for his continuous help, and finding and
+optimizing some of the computing expensive parts.
+
+udev 130
+========
+Bugfixes.
+
+Kernel devices and device nodes are connected now by reverse indizes in
+/sys and /dev. A device number retrieved by a stat() or similar, the
+kernel device directory can be found by looking up:
+  /sys/dev/{block,char}/<maj>:<min>
+and the device node of the same device by looking up:
+  /dev/{block,char}/<maj>:<min>
+
+udev 129
+========
+Fix recently introduced bug, which caused a compilation without large
+file support, where vol_id does not recognize raid signatures at the end
+of a volume.
+
+Firewire disks now create both, by-id/scsi-* and by-id/ieee-* links.
+Seems some kernel versions prevent the creation of the ieee-* links,
+so people used the scsi-* link which disappeared now.
+
+More libudev work. Almost all udevadm functionality comes from libudev
+now.
+
+udevadm trigger has a new option --type, which allows to trigger events
+for "devices", for "subsystems", or "failed" devices. The old option
+--retry-failed" still works, but is no longer mentioned in the man page.
+
+udev 128
+========
+Bugfixes.
+
+The udevadm info --device-id-of-file= output has changed to use
+the obvious format. Possible current users should use the --export
+option which is not affected.
+
+The old udev commands symlinks to udevadm are not installed, if
+these symlinks are used, a warning is printed.
+
+udev 127
+========
+Bugfixes.
+
+Optical drive's media is no longer probed for raid signatures,
+reading the end of the device causes some devices to malfunction.
+Also the offset of the last session found is used now to probe
+for the filesystem.
+
+The volume_id library got a major version number update to 1,
+some deprecated functions are removed.
+
+A shared library "libudev" gets installed now to provide access
+to udev device information. DeviceKit, the successor of HAL, will
+need this library to access the udev database and search sysfs for
+devices.
+The library is currently in an experimental state, also the API is
+expected to change, as long as the DeviceKit integration is not
+finished.
+
+udev 126
+========
+We use ./configure now. See INSTALL for details. Current
+options are:
+    --prefix=
+        "/usr" - prefix for man pages, include files
+    --exec-prefix=
+        "" - the root filesystem, prefix for libs and binaries
+    --sysconfdir=
+        "/etc"
+    --with-libdir-name=
+        "lib" - directory name for libraries, not a path name
+        multilib 64bit systems may use "lib64" instead of "lib"
+    --enable-debug
+        compile-in verbose debug messages
+    --disable-logging
+        disable all logging and compile-out all log strings
+    --with-selinux
+        link against SELInux libraries, to set the expected context
+        for created files
+
+In the default rules, the group "disk" gets permissions 0660 instead
+of 0640. One small step closer to unify distro rules. Some day, all
+distros hopefully end up with the same set of rules.
+
+No symlinks to udevadm are installed anymore, if they are still needed,
+they should be provided by the package.
+
+udev 125
+========
+Bugfixes.
+
+Default udev rules, which are not supposed to be edited by the user, should
+be placed in /lib/udev/rules.d/ now, to make it clear that they are private to
+the udev package and will be replaced with an update. Udev will pick up rule
+files from:
+  /lib/udev/rules.d/  - default installed rules
+  /etc/udev/rules.d/  - user rules + on-the-fly generated rules
+  /dev/.udev/rules.d/ - temporary non-persistent rules created after bootup
+It does not matter in which directory a rule file lives, all files are sorted
+in lexical order.
+
+To help creating /dev/root, we have now:
+  $ udevadm info --export --export-prefix="ROOT_" --device-id-of-file=/
+  ROOT_MAJOR=8
+  ROOT_MINOR=5
+In case the current --device-id-of-file is already used, please switch to
+the --export format version, it saves the output parsing and the old
+format will be changed to use ':' as a separator, like the format in the
+sysfs 'dev' file.
+
+udev 124
+========
+Fix cdrom_id to properly recognize blank media.
+
+udev 123
+========
+Bugfixes.
+
+Tape drive id-data is queried from /dev/bsg/* instead of the tape
+nodes. This avoids rewinding tapes on open().
+
+udev 122
+========
+Bugfixes.
+
+The symlinks udevcontrol and udevtrigger are no longer installed by
+the Makefile.
+
+The scsi_id program does not depend on sysfs anymore. It can speak
+SGv4 now, so /dev/bsg/* device nodes can be used, to query SCSI device
+data, which should solve some old problems with tape devices, where
+we better do not open all tape device nodes to identify the device.
+
+udev 121
+========
+Many bugfixes.
+
+The cdrom_id program is replaced by an advanced version, which can
+detect most common device types, and also properties of the inserted
+media. This is part of moving some basic functionality from HAL into
+udev (and the kernel).
+
+udev 120
+========
+Bugfixes.
+
+The last WAIT_FOR_SYSFS rule is removed from the default rules.
+
+The symlinks to udevadm for the debugging tools: udevmonitor and
+udevtest are no longer created.
+
+The symlinks to the udevadm man page for the old tool names are
+no longer created.
+
+Abstract namespace sockets paths in RUN+="socket:@<path>" rules,
+should be prefixed with '@' to indicate that the path is not a
+real file.
+
+udev 119
+========
+Bugfixes.
+
+udev 118
+========
+Bugfixes.
+
+Udevstart is removed from the tree, it did not get installed for
+a long time now, and is long replaced by trigger and settle.
+
+udev 117
+========
+Bugfixes.
+
+All udev tools are merged into a single binary called udevadm.
+The old names of the tools are built-in commands in udevadm now.
+Symlinks to udevadm, with the names of the old tools, provide
+the same functionality as the standalone tools. There is also
+only a single udevadm.8 man page left for all tools.
+
+Tools like mkinitramfs should be checked, if they need to include
+udevadm in the list of files.
+
+udev 116
+========
+Bugfixes.
+
+udev 115
+========
+Bugfixes.
+
+The etc/udev/rules.d/ directory now contains a default set of basic
+udev rules. This initial version is the result of a rules file merge
+of Fedora and openSUSE. For these both distros only a few specific
+rules are left in their own file, named after the distro. Rules which
+are optionally installed, because they are only valid for a specific
+architecture, or rules for subsystems which are not always used are
+in etc/udev/packages/.
+
+udev 114
+========
+Bugfixes.
+
+Dynamic rules can be created in /dev/.udev/rules.d/ to trigger
+actions by dynamically created rules.
+
+SYMLINK=="<value>" matches agains the entries in the list of
+currently defined symlinks. The links are not created in the
+filesystem at that point in time, but the values can be matched.
+
+RUN{ignore_error}+="<program>" will ignore any exit code from the
+program and not record as a failed event.
+
+udev 113
+========
+Bugfixes.
+
+Final merge of patches/features from the Ubuntu package.
+
+udev 112
+========
+Bugfixes.
+
+Control characters in filesystem label strings are no longer silenty
+removed, but hex-encoded, to be able to uniquely identify the device
+by its symlink in /dev/disk/by-label/.
+If libvolume_id is used by mount(8), LABEL= will work as expected,
+if slashes or other characters are used in the label string.
+
+To test the existence of a file, TEST=="<file>" and TEST!="<file>"
+can be specified now. The TEST key accepts an optional mode mask
+TEST{0100}=="<is executable file>".
+
+Scsi_id now supports a mode without expecting scsi-specific sysfs
+entries to allow the extraction of cciss-device persistent properties.
+
+udev 111
+========
+Bugfixes.
+
+In the future, we may see uuid's which are just simple character
+strings (see the DDF Raid Specification). For that reason vol_id now
+exports ID_FS_UUID_SAFE, just like ID_FS_LABEL_SAFE. For things like
+the creation of symlinks, the *_SAFE values ensure, that no control
+or whitespace characters are used in the filename.
+
+Possible users of libvolume_id, please use the volume_id_get_* functions.
+The public struct will go away in a future release of the library.
+
+udev 110
+========
+Bugfixes.
+
+Removal of useless extras/eventrecorder.sh.
+
+udev 109
+========
+Bugfixes.
+
+udev 108
+========
+Bugfixes.
+
+The directory multiplexer for dev.d/ and hotplug.d are finally removed
+from the udev package.
+
+udev 107
+========
+Bugfixes.
+
+Symlinks can have priorities now, the priority is assigned to the device
+and specified with OPTIONS="link_priority=100". Devices with higher
+priorities overwrite the symlinks of devices with lower priorities.
+If the device that currently owns the link, goes away, the symlink
+will be removed, and recreated, pointing to the next device with the
+highest actual priority. This should make /dev/disk/by-{label,uuid,id}
+more reliable, if multiple devices contain the same metadata and overwrite
+these symlinks.
+
+The dasd_id program is removed from the udev tree, and dasdinfo, with the
+needed rules, are part of the s390-tools now.
+
+Please add KERNEL=="[0-9]*:[0-9]*" to the scsi wait-for-sysfs rule,
+we may get the scsi sysfs mess fixed some day, and this will only catch
+the devices we are looking for.
+
+USB serial numbers for storage devices have the target:lun now appended,
+to make it possibble to distinguish broken multi-lun devices with all
+the same SCSI identifiers.
+
+Note: The extra "run_directory" which searches and executes stuff in
+/etc/hotplug.d/ and /etc/dev.d/ is long deprecated, and will be removed
+with the next release. Make sure, that you don't use it anymore, or
+provides your own implementation of that inefficient stuff.
+We are tired of reports about a "slow udev", because these directories
+contain stuff, that runs with _every_ event, instead of using rules,
+that run programs only for the matching events.
+
+udev 106
+========
+Bugfixes.
+
+udev 105
+========
+Bugfixes.
+
+DRIVER== will match only for devices that actually have a real
+driver. DRIVERS== must be used, if parent devices should be
+included in the match.
+
+Libvolume_id's "linux_raid" detection needed another fix.
+
+udev 104
+========
+Bugfixes.
+
+udev 103
+========
+Add additional check to volume_id detection of via_raid, cause
+some company decided to put a matching pattern all over the empty
+storage area of their music players.
+
+udev 102
+========
+Fix path_id for SAS devices.
+
+udev 101
+========
+The udev daemon can be started with --debug-trace now, which will
+execute all events serialized to get a chance to catch a possible
+action that crashes the box.
+
+A warning is logged, if PHYSDEV* keys, the "device" link, or a parent
+device attribute like $attr{../file} is used, only WAIT_FOR_SYSFS rules
+are excluded from the warning. Referencing parent attributes directly
+may break when something in the kernel driver model changes. Udev will
+just find the attribute by walking up the parent chain.
+
+Udevtrigger now sorts the list of devices depending on the device
+dependency, so a "usb" device is triggered after the parent "pci"
+device.
+
+udev 100
+========
+Revert persistent-storage ata-serial '_' '-' replacement.
+
+udev 099
+========
+Bugfixes.
+
+Udevtrigger can now filter the list of devices to be triggered. Matches
+for subsystems or sysfs attributes can be specified.
+
+The entries in /dev/.udev/queue and /dev/.udev/failed have changed to
+zero-sized files to avoid pointing to /sys and confuse broken tools which
+scan the /dev directory. To retry failed events, udevtrigger --retry-failed
+should be used now.
+
+The rules and scripts to create udev rules for persistent network
+devices and optical drives are in the extras/rules_generator directory
+now. If you use something similar, please consider replacing your own
+version with this, to share the support effort. The rule_generator
+installs its own rules into /etc/udev/rules.d.
+
+The cdrom_id tool installs its own rule now in /etc/udev/rules.d, cause
+the rule_generator depends on cdrom_id to be called in an earlier rule.
+
+udev 098
+========
+Bugfixes.
+
+Renaming of some key names (the old names still work):
+BUS -> SUBSYSTEMS, ID -> KERNELS, SYSFS -> ATTRS, DRIVER -> DRIVERS.
+(The behavior of the key DRIVER will change soon in one of the next
+releases, to match only the event device, please switch to DRIVERS
+instead. If DRIVER is used, it will behave like DRIVERS, but an error
+is logged.
+With the new key names, we have a more consistent and simpler scheme.
+We can match the properties of the event device only, with: KERNEL,
+SUBSYSTEM, ATTR, DRIVER. Or include all the parent devices in the match,
+with: KERNELS, SUBSYSTEMS, ATTRS, DRIVERS. ID, BUS, SYSFS, DRIVER are no
+longer mentioned in the man page and should be switched in the rule
+files.
+
+ATTR{file}="value" can be used now, to write to a sysfs file of the
+event device. Instead of:
+  ..., SYSFS{type}=="0|7|14", RUN+="/bin/sh -c 'echo 60 > /sys$$DEVPATH/timeout'"
+we now can do:
+  ..., ATTR{type}=="0|7|14", ATTR{timeout}="60"
+
+All the PHYSDEV* keys are deprecated and will be removed from a
+future kernel:
+  PHYDEVPATH -    is the path of a parent device and should not be
+                  needed at all.
+  PHYSDEVBUS -    is just a SUBSYSTEM value of a parent, and can be
+                  matched with SUBSYSTEMS==
+  PHYSDEVDRIVER - for bus devices it is available as ENV{DRIVER}.
+                  Newer kernels will have DRIVER in the environment,
+                  for older kernels udev puts in. Class device will
+                  no longer carry this property of a parent and
+                  DRIVERS== can be used to match such a parent value.
+Note that ENV{DRIVER} is only available for a few bus devices, where
+the driver is already bound at device event time. On coldplug, the
+events for a lot devices are already bound to a driver, and they will have
+that value set. But on hotplug, at the time the kernel creates the device,
+it can't know what driver may claim the device after that, therefore
+in most cases it will be empty.
+
+Failed events should now be re-triggered with:
+   udevtrigger --retry-failed.
+Please switch to this command, so we keep the details of the /dev/.udev/failed/
+files private to the udev tools. We may need to switch the current symlink
+target, cause some obviously broken tools try to scan all files in /dev
+including /dev/.udev/, find the links to /sys and end up stat()'ing sysfs files
+million times. This takes ages on slow boxes.
+
+The udevinfo attribute walk (-a) now works with giving a device node
+name (-n) instead of a devpath (-p). The query now always works, also when
+no database file was created by udev.
+
+The built-in /etc/passwd /etc/group parser is removed, we always depend on
+getpwnam() and getgrnam() now. One of the next releases will depend on
+fnmatch() and may use getopt_long().
+
+udev 097
+========
+Bugfixes and small improvements.
+
+udev 096
+========
+Fix path_id for recent kernels.
+
+udev 095
+========
+%e is finally gone.
+
+Added support for swapping network interface names, by temporarily
+renaming the device and wait for the target name to become free.
+
+udev 094
+========
+The built-in MODALIAS key and substitution is removed.
+
+udev 093
+========
+The binary firmware helper is replaced by the usual simple
+shell script. Udevsend is removed from the tree.
+
+udev 092
+========
+Bugfix release.
+
+udev 091
+========
+Some more keys require the correct use of '==' and '=' depending
+on the kind of operation beeing an assignment or a match. Rules
+with invalid operations are skipped and logged to syslog. Please
+test with udevtest if the parsing of your rules throws errors and
+fix possibly broken rules.
+
+udev 090
+========
+Provide "udevsettle" to wait for all current udev events to finish.
+It also watches the current kernel netlink queue by comparing the
+even sequence number to make sure that there are no current pending
+events that have not already arrived in the daemon.
+
+udev 089
+========
+Fix rule to skip persistent rules for removable IDE devices, which
+also skipped optical IDE drives.
+
+All *_id program are installed in /lib/udev/ by default now.
+
+No binary is stripped anymore as this should be done in the
+packaging process and not at build time.
+
+libvolume_id is provided as a shared library now and vol_id is
+linked against it. Also one of the next HAL versions will require
+this library, and the HAL build process will also require the
+header file to be installed. The copy of the same code in HAL will
+be removed to have only a single copy left on the system.
+
+udev 088
+========
+Add persistent links for SCSI tapes. The rules file is renamed
+to 60-persistent-storage.rules.
+
+Create persistent path for usb devices. Can be used for all sorts
+of devices that can't be distinguished by other properties like
+multiple identical keyboards and mice connected to the same box.
+
+Provide "udevtrigger" program to request events on coldplug. The
+shell script is much too slow with thousends of devices.
+
+udev 087
+========
+Fix persistent disk rules to exclude removable IDE drives.
+
+Warn if %e, $modalias or MODALIAS is used.
+
+udev 086
+========
+Fix queue export, which wasn't correct for subsequent add/remove
+events for the same device.
+
+udev 085
+========
+Fix cramfs detection on big endian.
+
+Make WAIT_FOR_SYSFS usable in "normal" rules and silent if the whole
+device goes away.
+
+udev 084
+========
+If BUS== and SYSFS{}== have been used in the same rule, the sysfs
+attributes were only checked at the parent device that matched the
+by BUS requested subsystem. Fix it to also look at the device we
+received the event for.
+
+Build variable CROSS has changed to CROSS_COMPILE to match the kernel
+build name.
+
+udev 083
+========
+Fix a bug where NAME="" would prevent RUN from beeing executed.
+
+RUN="/bin/program" does not longer automatically add the subsystem
+as the first parameter. This is from the days of /sbin/hotplug
+which is dead now and it's just confusing to need to add a space at
+the end of the program name to prevent this.
+If you use rules that need the subsystem as the first parameter,
+like the old "udev_run_hotlugd" and "udev_run_devd", add the subsystem
+to the key like RUN+="/bin/program $env{SUBSYSTEM}".
+
+udev 082
+========
+The udev man page has moved to udev(7) as it does not describe a command
+anymore. The programs udev, udevstart and udevsend are no longer installed
+by default and must be copied manually, if they should be installed or
+included in a package.
+
+Fix a bug where "ignore_device" could run earlier collected RUN keys before
+the ignore rule was applied.
+
+More preparation for future sysfs changes. usb_id and scsi_id no longer
+depend on a magic order of devices in the /devices chain. Specific devices
+should be requested by their subsytem.
+
+This will always find the scsi parent device without depending on a specific
+path position:
+  dev = sysfs_device_get(devpath);
+  dev_usb = sysfs_device_get_parent_with_subsystem(dev, "scsi");
+
+The "device" link in the current sysfs layout will be automatically
+_resolved_ as a parent and in the new sysfs layout it will just _be_ the
+parent in the devpath. If a device is requested by it's symlink, like all
+class devices in the new sysfs layout will look like, it gets automatically
+resolved and substituted with the real devpath and not the symlink path.
+
+Note:
+A similar logic must be applied to _all_ sysfs users, including
+scripts, that search along parent devices in sysfs. The explicit use of
+the "device" link must be avoided. With the future sysfs layout all
+DEVPATH's will start with /devices/ and have a "subsystem" symlink poiting
+back to the "class" or the "bus". The layout of the parent devices in
+/devices is not necessarily expected to be stable across kernel releases and
+searching for parents by their subsystem should make sysfs users tolerant
+for changed parent chains.
+
+udev 081
+========
+Prepare udev to work with the experimental kernel patch, that moves
+/sys/class devices to /sys/devices and /sys/block to /sys/class/block.
+
+Clarify BUS, ID, $id usage and fix $id behavior. This prepares for
+moving the class devices to /sys/devices.
+
+Thanks again to Marco for help finding a hopefully nice compromise
+to make %b simpler and working again.
+
+udev 080
+========
+Complete removal of libsysfs, replaced by simple helper functions
+which are much simpler and a bit faster. The udev daemon operatesentirely
+on event parameters and does not use sysfs for simple rules anymore.
+Please report any new bugs/problems, that may be caused by this big
+change. They will be fixed immediately.
+
+The enumeration format character '%e' is deprecated and will be
+removed sometimes from a future udev version. It never worked correctly
+outside of udevstart, so we can't use it with the new parallel
+coldplug. A simple enumeration is as useless as the devfs naming
+scheme, just get rid of both if you still use it.
+
+MODALIAS and $modalias is not needed and will be removed from one of
+the next udev versions, replace it in all rules with ENV{MODALIAS} or
+the sysfs "modalias" value.
+
+Thanks a lot to Marco for all his help on finding and fixing bugs.
+
+udev 079
+========
+Let scsi_id request libata drive serial numbers from page 0x80.
+
+Renamed etc/udev/persistent.rules to persistent-disk.rules and
+added /dev/disk/by-name/* for device mapper device names.
+
+Removed %e from the man page. It never worked reliably outside
+of udevstart and udevstart is no longer recommended to use.
+
+udev 078
+========
+Symlinks are now exported to the event environment. Hopefully it's no
+longer needed to run udevinfo from an event process, like it was
+mentioned on the hotplug list:
+  UDEV  [1134776873.702967] add@/block/sdb
+  ...
+  DEVNAME=/dev/sdb
+  DEVLINKS=/dev/disk/by-id/usb-IBM_Memory_Key_0218B301030027E8 /dev/disk/by-path/usb-0218B301030027E8:0:0:0
+
+udev 077
+========
+Fix a problem if udevsend is used as the hotplug handler and tries to use
+syslog, which causes a "vc" event loop. 2.6.15 will make udevsend obsolete
+and this kind of problems will hopefully go away soon.
+
+udev 076
+========
+All built-in logic to work around bad sysfs timing is removed with this
+version. The need to wait for sysfs files is almost fixed with a kernel
+version that doesn't work with this udev version anyway. Until we fix
+the timing of the "bus" link creation, the former integrated logic should
+be emulated by a rule placed before all other rules:
+  ACTION=="add", DEVPATH=="/devices/*", ENV{PHYSDEVBUS}=="?*", WAIT_FOR_SYSFS="bus"
+
+The option "udev_db" does no longer exist. All udev state will be in
+/$udev_root/.udev/ now, there is no longer an option to set this
+to anything else.
+If the init script or something else used this value, just depend on
+this hardcoded path. But remember _all_content_ of this directory is
+still private to udev and can change at any time.
+
+Default location for rule sripts and helper programs is now: /lib/udev/.
+Everything that is not useful on the commandline should go into this
+directory. Some of the helpers in the extras folder are installed there
+now. The rules need to be changed, to find the helpers there.
+
+Also /lib/udev/devices is recommended as a directory where packages or
+the user can place real device nodes, which get copied over to /dev at
+every boot. This should replace the various solutions with custom config
+files.
+
+Udevsend does no longer start the udev daemon. This must be done with
+the init script that prepares /dev on tmpfs and creates the initial nodes,
+before starting the daemon.
+
+udev 075
+========
+Silent a too verbose error logging for the old hotplug.d/ dev.d/
+emulation.
+
+The copy of klibc is removed. A systemwide installed version of klibc
+should be used to build a klibc udev now.
+
+udev 074
+========
+NAME="" will not create any nodes, but execute RUN keys. To completely
+ignore an event the OPTION "ignore_device" should be used.
+
+After removal of the reorder queue, events with a TIMEOUT can be executed
+without any queuing now.
+
+udev 073
+========
+Fixed bug in udevd, if inotify is not available. We depend on netlink
+uevents now, kernels without that event source will not work with that
+version of udev anymore.
+
+udev 072
+========
+The rule parsing happens now in the daemon once at startup, all udev
+event processes inherit the already parsed rules from the daemon.
+It is shipped with SUSE10.0 and reduces heavily the system load at
+startup. The option to save precompiled rules and let the udev process
+pick the them up is removed, as it's no longer needed.
+
+Kernel 2.6.15 will have symlinks at /class/input pointing to the real
+device. Libsysfs is changed to "translate" the requested link into the
+real device path, as it would happen with the hotplug event. Otherwise
+device removal and the udev database will not work.
+
+Using 'make STRIPCMD=' will leave the binaries unstripped for debugging
+and packaging.
+
+A few improvements for vol_id, the filesytem probing code.
+
+udev 071
+========
+Fix a stupid typo in extras/run_directory for "make install".
+
+scsi_id creates the temporary devnode now in /dev for usage with a
+non-writable /tmp directory.
+
+The uevent kernel socket buffer can carry app. 50.000 events now,
+let's see who can break this again. :)
+
+The upcoming kernel will have a new input driver core integration.
+Some class devices are now symlinks to the real device. libsysfs
+needs a fix for this to work correctly. Udevstart of older udev
+versions will _not_ create these devices!
+
+udev 070
+========
+Fix a 'install' target in the Makefile, that prevents EXTRAS from
+beeing installed.
+
+udev 069
+========
+A bunch of mostly trivial bugfixes. From now on no node name or
+symlink name can contain any character than plain whitelisted ascii
+characters or validated utf8 byte-streams. This is needed for the
+/dev/disk/by-label/* links, because we import untrusted data and
+export it to the filesystem.
+
+udev 068
+========
+More bugfixes. If udevd was started from the kernel, we don't
+have stdin/stdout/stderr, which broke the forked tools in some
+situations.
+
+udev 067
+========
+Bugfix. udevstart event ordering was broken for a long time.
+The new run_program() uncovered it, because /dev/null was not
+available while we try to run external programs.
+Now udevstart should create it before we run anything.
+
+udev 066
+========
+Minor bugfixes and some distro rules updates. If you don't have the
+persistent disk rules in /dev/disk/by-*/* on your distro, just
+grab it from here. :)
+
+udev 065
+========
+We can use socket communication now to pass events from udev to
+other programs:
+  RUN+="socket:/org/freedesktop/hal/udev_event"
+will pass the whole udev event to the HAL daemon without the need
+for a forked helper. (See ChangeLog for udevmonitor, as an example)
+
+udev 064
+========
+Mostly bugfixes and see ChangeLog.
+
+The test for the existence of an environment value should be
+switched from:
+  ENV{KEY}=="*" to ENV{KEY}=="?*"
+because "*" will not fail anymore, if the key does not exist or
+is empty.
+
+udev 063
+========
+Bugfixes and a few tweaks described in the ChangeLog.
+
+udev 062
+========
+Mostly a Bugfix release.
+
+Added WAIT_FOR_SYSFS="<attribute>" to be able to fight against the sysfs
+timing with custom rules.
+
+udev 061
+========
+We changed the  internal rule storage format. Our large rule files took
+2 MB of RAM, with the change we are down to 99kB.
+
+If the device-node has been created with default name and no symlink or
+options are to remenber, it is not longer stored in the udevdb. HAL will
+need to be updated to work correctly with that change.
+
+To overrride optimization flags, OPTFLAGS may be used now.
+
+udev 060
+========
+Bugfix release.
+
+udev 059
+========
+Major changes happened with this release. The goal is to take over the
+complete kernel-event handling and provide a more efficient way to dispatch
+kernel events. Replacing most of the current shell script logic and the
+kernel forked helper with a netlink-daemon and a rule-based event handling.
+
+o udevd listens to netlink events now. The first valid netlink event
+  will make udevd ignore any message from udevsend that contains a
+  SEQNUM, to avoid duplicate events. The forked events can be disabled
+  with:
+    echo "" > /proc/sys/kernel/hotplug
+  For full support, the broken input-subsytem needs to be fixed, not to
+  bypass the driver core.
+
+o /etc/dev.d/ + /etc/hotplug.d/ directory multiplexing is completely
+  removed from udev itself and must be emulated by calling small
+  helper binaries provided in the extras folder:
+    make EXTRAS=extras/run_directory/
+  will build udev_run_devd and udev_run_hotplugd, which can be called
+  from a rule if needed:
+    RUN+="/sbin/udev_run_hotplugd"
+  The recommended way to handle this is to convert all the calls from
+  the directories to explicit udev rules and get completely rid of the
+  multiplexing. (To catch a ttyUSB event, you now no longer need to
+  fork and exit 300 tty script instances you are not interested in, it
+  is just one rule that matches exactly the device.)
+
+o udev handles now _all_ events not just events for class and block
+  devices, this way it is possible to control the complete event
+  behavior with udev rules. Especially useful for rules like:
+    ACTION="add", DEVPATH="/devices/*", MODALIAS=="?*", RUN+="/sbin/modprobe $modalias"
+
+o As used in the modalias rule, udev supports now textual
+  substitution placeholder along with the usual format chars. This
+  needs to be documented, for now it's only visible in udev_rules_parse.c.
+
+o The rule keys support now more operations. This is documented in the
+  man page. It is possible to add values to list-keys like the SYMLINK
+  and RUN list with KEY+="value" and to clear the list by assigning KEY="".
+  Also "final"-assignments are supported by using KEY:="value", which will
+  prevent changing the key by any later rule.
+
+o kernel 2.6.12 has the "detached_state" attribute removed from
+  sysfs, which was used to recognize sysfs population. We switched that
+  to wait for the "bus" link, which is only available in kernels after 2.6.11.
+  Running this udev version on older kernels may cause a short delay for
+  some events.
+
+o To provide infrastructure for persistent device naming, the id programs:
+  scsi_id, vol_id (former udev_volume_id), and ata_id (new) are able now
+  to export the probed data in environment key format:
+    pim:~ # /sbin/ata_id --export /dev/hda
+    ID_MODEL=HTS726060M9AT00
+    ID_SERIAL=MRH401M4G6UM9B
+    ID_REVISION=MH4OA6BA
+
+  The following rules:
+    KERNEL="hd*[!0-9]", IMPORT="/sbin/ata_id --export $tempnode"
+    KERNEL="hd*[!0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_MODEL}_$env{ID_SERIAL}"
+
+  Will create:
+    kay@pim:~> tree /dev/disk
+    /dev/disk
+    |-- by-id
+    |   |-- HTS726060M9AT00_MRH401M4G6UM9B -> ../../hda
+    |   `-- IBM-Memory_Key -> ../../sda
+    |-- by-label
+    |   |-- swap -> ../../hda1
+    |   |-- date -> ../../sda1
+    |   `-- home -> ../../hda3
+    `-- by-uuid
+        |-- 2E08712B0870F2E7 -> ../../hda3
+        |-- 9352cfef-7687-47bc-a2a3-34cf136f72e1 -> ../../hda1
+        |-- E845-7A89 -> ../../sda1
+        `-- b2a61681-3812-4f13-a4ff-920d70604299 -> ../../hda2
+
+  The IMPORT= operation will import these keys in the environment and make
+  it available for later PROGRAM= and RUN= executed programs. The keys are
+  also stored in the udevdb and can be queried from there with one of the
+  next udev versions.
+
+o A few binaries are silently added to the repository, which can be used
+  to replay kernel events from initramfs instead of using coldplug. udevd
+  can be instructed now to queue-up events while the stored events from
+  initramfs are filled into the udevd-queue. This code is still under
+  development and there is no documentation now besides the code itself.
+  The additional binaries get compiled, but are not installed by default.
+
+o There is also a temporary fix for a performance problem where too many
+  events happen in parallel and every event needs to parse the rules.
+  udev can now read precompiled rules stored on disk. This is likely to be
+  replaced by a more elegant solution in a future udev version.
+
+udev 058
+========
+With kernel version 2.6.12, the sysfs file "detached_state" was removed.
+Fix for libsysfs not to expect this file was added.
+
+udev 057
+========
+All rules are applied now, but only the first matching rule with a NAME-key
+will be applied. All later rules with NAME-key are completely ignored. This
+way system supplied symlinks or permissions gets applied to user-defined
+naming rules.
+
+Note:
+Please check your rules setup, if you may need to add OPTIONS="last_rule"
+to some rules, to keep the old behavior.
+
+The rules are read on "remove"-events too. That makes is possible to match
+with keys that are available on remove (KERNEL, SUBSYSTEM, ID, ENV, ...) to
+instruct udev to ignore an event (OPTIONS="ignore_device").
+The new ACTION-key may be used to let a rule act only at a "remove"-event.
+
+The new RUN-key supports rule-based execution of programs after device-node
+handling. This is meant as a general replacement for the dev.d/-directories
+to give fine grained control over the execution of programs.
+
+The %s{}-sysfs format char replacement values are searched at any of the
+devices in the device chain now, not only at the class-device.
+
+We support log priority levels now. The value udev_log in udev.conf is used
+to determine what is printed to syslog. This makes it possible to
+run a version with compiled-in debug messages in a production environment
+which is sometimes needed to find a bug.
+It is still possible to supress the inclusion of _any_ syslog usage with
+USE_LOG=false to create the smallest possible binaries if needed.
+The configured udev_log value can be overridden with the environment variable
+UDEV_LOG.
+
+udev 056
+========
+Possible use of a system-wide klibc:
+  make USE_KLIBC=true KLCC=/usr/bin/klcc all
+will link against an external klibc and our own version will be ignored.
+
+udev 055
+========
+We support an unlimited count of symlinks now.
+
+If USE_STATIC=true is passed to a glibc build, we link statically and use
+a built-in userdb parser to resolve user and group names.
+
+The PLACE= key is gone. It can be replaced by an ID= for a long time, because
+we walk up the chain of physical devices to find a match.
+
+The KEY="<value>" format supports '=', '==', '!=,' , '+=' now. This makes it
+easy to skip certain attribute matches without composing rules with weird
+character class negations like:
+  KERNEL="[!s][!c][!d]*"
+this can now be replaced with:
+  KERNEL!="scd*"
+The current simple '=' is still supported, and should work as it does today,
+but existing rules should be converted if possible, to be better readable.
+
+We have new ENV{}== key now, to match against a maximum of 5 environment
+variables.
+
+udevstart is its own binary again, because we don't need co carry this araound
+with every forked event.
diff --git a/README b/README
new file mode 100644
index 0000000..38459c6
--- /dev/null
+++ b/README
@@ -0,0 +1,101 @@
+udev - Linux userspace device management
+
+Integrating udev in the system has complex dependencies and may differ from
+distribution to distribution. A system may not be able to boot up or work
+reliably without a properly installed udev version. The upstream udev project
+does not recommend replacing a distro's udev installation with the upstream
+version.
+
+The upstream udev project's set of default rules may require a most recent
+kernel release to work properly.
+
+Tools and rules shipped by udev are not public API and may change at any time.
+Never call any private tool in /usr/lib/udev from any external application; it
+might just go away in the next release. Access to udev information is only offered
+by udevadm and libudev. Tools and rules in /usr/lib/udev and the entire contents
+of the /run/udev directory are private to udev and do change whenever needed.
+
+Requirements:
+  - Version 2.6.34 of the Linux kernel with sysfs, procfs, signalfd, inotify,
+    unix domain sockets, networking and hotplug enabled
+
+  - Some architectures might need a later kernel, that supports accept4(),
+    or need to backport the accept4() syscall wiring in the kernel.
+
+  - These options are required:
+      CONFIG_DEVTMPFS=y
+      CONFIG_HOTPLUG=y
+      CONFIG_INOTIFY_USER=y
+      CONFIG_NET=y
+      CONFIG_PROC_FS=y
+      CONFIG_SIGNALFD=y
+      CONFIG_SYSFS=y
+      CONFIG_SYSFS_DEPRECATED*=n
+      CONFIG_UEVENT_HELPER_PATH=""
+
+  - These options might be needed:
+      CONFIG_BLK_DEV_BSG=y (SCSI devices)
+      CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes)
+
+  - The /dev directory needs the 'devtmpfs' filesystem mounted.
+    Udev only manages the permissions and ownership of the
+    kernel-provided device nodes, and possibly creates additional symlinks.
+
+  - Udev requires /run to be writable, which is usually done by mounting a
+    'tmpfs' filesystem.
+
+  - This version of udev does not work properly with the CONFIG_SYSFS_DEPRECATED*
+    option enabled.
+
+  - The deprecated hotplug helper /sbin/hotplug should be disabled in the
+    kernel configuration, it is not needed today, and may render the system
+    unusable because the kernel may create too many processes in parallel
+    so that the system runs out-of-memory.
+
+  - The proc filesystem must be mounted on /proc, and the sysfs filesystem must
+    be mounted at /sys. No other locations are supported by a standard
+    udev installation.
+
+  - The default rule sset requires the following group names resolvable at udev startup:
+      disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, and kmem.
+    Especially in LDAP setups, it is required that getgrnam() be able to resolve
+    these group names with only the rootfs mounted and while no network is
+    available.
+
+  - Some udev extras have external dependencies like:
+      libglib2, usbutils, pciutils, and gperf.
+    All these extras can be disabled with configure options.
+
+Setup:
+  - The udev daemon should be started to handle device events sent by the kernel.
+    During bootup, the events for already existing devices can be replayed, so
+    that they are configured by udev. The systemd service files contain the
+    needed commands to start the udev daemon and the coldplug sequence.
+
+  - Restarting the daemon never applies any rules to existing devices.
+
+  - New/changed rule files are picked up automatically; there is usually no
+    daemon restart or signal needed.
+
+Operation:
+  - Based on events the kernel sends out on device creation/removal, udev
+    creates/removes device nodes and symlinks in the /dev directory.
+
+  - All kernel events are matched against a set of specified rules, which
+    possibly hook into the event processing and load required kernel
+    modules to set up devices. For all devices, the kernel exports a major/minor
+    number; if needed, udev creates a device node with the default kernel
+    device name. If specified, udev applies permissions/ownership to the device
+    node, creates additional symlinks pointing to the node, and executes
+    programs to handle the device.
+
+  - The events udev handles, and the information udev merges into its device
+    database, can be accessed with libudev:
+      http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
+      http://www.kernel.org/pub/linux/utils/kernel/hotplug/gudev/
+
+For more details about udev and udev rules, see the udev man pages:
+      http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/
+
+Please direct any comment/question to the linux-hotplug mailing list at:
+  linux-hotplug@vger.kernel.org
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..8b8b9c8
--- /dev/null
+++ b/TODO
@@ -0,0 +1,22 @@
+ - find a way to tell udev to not cancel firmware
+   requests in initramfs
+
+ - scsi_id -> sg3_utils?
+
+ - make gtk-doc optional like kmod
+
+ - move /usr/lib/udev/devices/ to tmpfiles
+
+ - trigger --subsystem-match=usb/usb_device
+
+ - kill rules_generator
+
+ - have a $attrs{} ?
+
+ - remove RUN+="socket:"
+
+ - libudev.so.1
+     - symbol versioning
+     - return object with *_unref()
+     - udev_monitor_from_socket()
+     - udev_queue_get_failed_list_entry()
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..55ee03a
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,44 @@
+#!/bin/sh -e
+
+if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then
+        cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \
+        chmod +x .git/hooks/pre-commit && \
+        echo "Activated pre-commit hook."
+fi
+
+gtkdocize
+autoreconf --install --symlink
+
+libdir() {
+        echo $(cd $1/$(gcc -print-multi-os-directory); pwd)
+}
+
+args="$args \
+--prefix=/usr \
+--sysconfdir=/etc \
+--libdir=$(libdir /usr/lib) \
+--with-selinux \
+--enable-gtk-doc"
+
+if [ -L /bin ]; then
+args="$args \
+--libexecdir=/usr/lib \
+--with-systemdsystemunitdir=/usr/lib/systemd/system \
+"
+else
+args="$args \
+--with-rootprefix= \
+---with-rootlibdir=$(libdir /lib) \
+--bindir=/sbin \
+--libexecdir=/lib \
+--with-systemdsystemunitdir=/lib/systemd/system \
+"
+fi
+
+echo
+echo "----------------------------------------------------------------"
+echo "Initialized build system. For a common configuration please run:"
+echo "----------------------------------------------------------------"
+echo
+echo "./configure CFLAGS='-g -O1' $args"
+echo
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b31b62f
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,242 @@
+AC_PREREQ(2.60)
+AC_INIT([udev],
+       [182],
+       [linux-hotplug@vger.kernel.org],
+       [udev],
+       [http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html])
+AC_CONFIG_SRCDIR([src/udevd.c])
+AC_CONFIG_AUX_DIR([build-aux])
+AM_INIT_AUTOMAKE([check-news foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects])
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
+AC_CONFIG_MACRO_DIR([m4])
+AM_SILENT_RULES([yes])
+LT_INIT([disable-static])
+AC_PROG_AWK
+AC_PROG_SED
+AC_PROG_MKDIR_P
+GTK_DOC_CHECK(1.10)
+AC_PREFIX_DEFAULT([/usr])
+
+AC_PATH_PROG([XSLTPROC], [xsltproc])
+AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC" != x)
+
+AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([POSIX RT library not found])])
+
+PKG_CHECK_MODULES(BLKID, blkid >= 2.20)
+
+PKG_CHECK_MODULES(KMOD, libkmod >= 5)
+
+AC_ARG_WITH([rootprefix],
+       AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]),
+       [], [with_rootprefix=${ac_default_prefix}])
+AC_SUBST([rootprefix], [$with_rootprefix])
+
+AC_ARG_WITH([rootlibdir],
+       AS_HELP_STRING([--with-rootlibdir=DIR], [rootfs directory to install shared libraries]),
+       [], [with_rootlibdir=$libdir])
+AC_SUBST([rootlib_execdir], [$with_rootlibdir])
+
+AC_ARG_WITH([selinux],
+       AS_HELP_STRING([--with-selinux], [enable SELinux support]),
+       [], [with_selinux=no])
+AS_IF([test "x$with_selinux" = "xyes"], [
+       LIBS_save=$LIBS
+       AC_CHECK_LIB(selinux, getprevcon,
+              [],
+              AC_MSG_ERROR([SELinux selected but libselinux not found]))
+       LIBS=$LIBS_save
+       SELINUX_LIBS="-lselinux -lsepol"
+       AC_DEFINE(WITH_SELINUX, [1] ,[SELinux support.])
+])
+AC_SUBST([SELINUX_LIBS])
+AM_CONDITIONAL(WITH_SELINUX, [test "x$with_selinux" = "xyes"])
+
+AC_ARG_ENABLE([debug],
+       AS_HELP_STRING([--enable-debug], [enable debug messages @<:@default=disabled@:>@]),
+       [], [enable_debug=no])
+AS_IF([test "x$enable_debug" = "xyes"], [ AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.]) ])
+
+AC_ARG_ENABLE([logging],
+       AS_HELP_STRING([--disable-logging], [disable system logging @<:@default=enabled@:>@]),
+       [], enable_logging=yes)
+AS_IF([test "x$enable_logging" = "xyes"], [ AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) ])
+
+AC_ARG_ENABLE([manpages],
+        AS_HELP_STRING([--disable-manpages], [disable man pages @<:@default=enabled@:>@]),
+        [], enable_manpages=yes)
+AM_CONDITIONAL([ENABLE_MANPAGES], [test "x$enable_manpages" = "xyes"])
+
+if test "x$cross_compiling" = "xno" ; then
+       AC_CHECK_FILES([/usr/share/pci.ids], [pciids=/usr/share/pci.ids])
+       AC_CHECK_FILES([/usr/share/hwdata/pci.ids], [pciids=/usr/share/hwdata/pci.ids])
+       AC_CHECK_FILES([/usr/share/misc/pci.ids], [pciids=/usr/share/misc/pci.ids])
+fi
+
+AC_ARG_WITH(usb-ids-path,
+       [AS_HELP_STRING([--with-usb-ids-path=DIR], [Path to usb.ids file])],
+       [USB_DATABASE=${withval}],
+       [if test -n "$usbids" ; then
+              USB_DATABASE="$usbids"
+       else
+              PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82)
+              AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)])
+       fi])
+AC_MSG_CHECKING([for USB database location])
+AC_MSG_RESULT([$USB_DATABASE])
+AC_SUBST(USB_DATABASE)
+
+AC_ARG_WITH(pci-ids-path,
+       [AS_HELP_STRING([--with-pci-ids-path=DIR], [Path to pci.ids file])],
+       [PCI_DATABASE=${withval}],
+       [if test -n "$pciids" ; then
+              PCI_DATABASE="$pciids"
+       else
+              AC_MSG_ERROR([pci.ids not found, try --with-pci-ids-path=])
+       fi])
+AC_MSG_CHECKING([for PCI database location])
+AC_MSG_RESULT([$PCI_DATABASE])
+AC_SUBST(PCI_DATABASE)
+
+AC_ARG_WITH(firmware-path,
+       AS_HELP_STRING([--with-firmware-path=DIR[[[:DIR[...]]]]],
+          [Firmware search path (default=ROOTPREFIX/lib/firmware/updates:ROOTPREFIX/lib/firmware)]),
+       [], [with_firmware_path="$rootprefix/lib/firmware/updates:$rootprefix/lib/firmware"])
+OLD_IFS=$IFS
+IFS=:
+for i in $with_firmware_path; do
+       if test "x${FIRMWARE_PATH}" = "x"; then
+              FIRMWARE_PATH="\\\"${i}/\\\""
+       else
+              FIRMWARE_PATH="${FIRMWARE_PATH}, \\\"${i}/\\\""
+       fi
+done
+IFS=$OLD_IFS
+AC_SUBST([FIRMWARE_PATH], [$FIRMWARE_PATH])
+
+AC_ARG_WITH([systemdsystemunitdir],
+       AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
+       [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ])
+AM_CONDITIONAL(WITH_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ])
+
+# ------------------------------------------------------------------------------
+# GUdev - libudev gobject interface
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([gudev],
+       AS_HELP_STRING([--disable-gudev], [disable Gobject libudev support @<:@default=enabled@:>@]),
+       [], [enable_gudev=yes])
+AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0]) ])
+
+AC_ARG_ENABLE([introspection],
+       AS_HELP_STRING([--disable-introspection], [disable GObject introspection @<:@default=enabled@:>@]),
+       [], [enable_introspection=yes])
+AS_IF([test "x$enable_introspection" = "xyes"], [
+       PKG_CHECK_MODULES([INTROSPECTION], [gobject-introspection-1.0 >= 0.6.2])
+       AC_DEFINE([ENABLE_INTROSPECTION], [1], [enable GObject introspection support])
+       AC_SUBST([G_IR_SCANNER], [$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)])
+       AC_SUBST([G_IR_COMPILER], [$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)])
+       AC_SUBST([G_IR_GENERATE], [$($PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0)])
+       AC_SUBST([GIRDIR], [$($PKG_CONFIG --define-variable=datadir=${datadir} --variable=girdir gobject-introspection-1.0)])
+       AC_SUBST([GIRTYPELIBDIR], [$($PKG_CONFIG --define-variable=libdir=${libdir} --variable=typelibdir gobject-introspection-1.0)])
+])
+AM_CONDITIONAL([ENABLE_INTROSPECTION], [test "x$enable_introspection" = "xyes"])
+AM_CONDITIONAL([ENABLE_GUDEV], [test "x$enable_gudev" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# keymap - map custom hardware's multimedia keys
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([keymap],
+       AS_HELP_STRING([--disable-keymap], [disable keymap fixup support @<:@default=enabled@:>@]),
+       [], [enable_keymap=yes])
+AS_IF([test "x$enable_keymap" = "xyes"], [
+       AC_PATH_PROG([GPERF], [gperf])
+       if test -z "$GPERF"; then
+              AC_MSG_ERROR([gperf is needed])
+       fi
+
+       AC_CHECK_HEADER([linux/input.h], [:], AC_MSG_ERROR([kernel headers not found]))
+       AC_SUBST([INCLUDE_PREFIX], [$(echo '#include <linux/input.h>' | eval $ac_cpp -E - | sed -n '/linux\/input.h/ {s:.*"\(.*\)/linux/input.h".*:\1:; p; q}')])
+])
+AM_CONDITIONAL([ENABLE_KEYMAP], [test "x$enable_keymap" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# mtd_probe - autoloads FTL module for mtd devices
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([mtd_probe],
+       AS_HELP_STRING([--disable-mtd_probe], [disable MTD support @<:@default=enabled@:>@]),
+       [], [enable_mtd_probe=yes])
+AM_CONDITIONAL([ENABLE_MTD_PROBE], [test "x$enable_mtd_probe" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# rule_generator - persistent network and optical device rule generator
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([rule_generator],
+       AS_HELP_STRING([--enable-rule_generator], [enable persistent network + cdrom links support @<:@default=disabled@:>@]),
+       [], [enable_rule_generator=no])
+AM_CONDITIONAL([ENABLE_RULE_GENERATOR], [test "x$enable_rule_generator" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# create_floppy_devices - historical floppy kernel device nodes (/dev/fd0h1440, ...)
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([floppy],
+       AS_HELP_STRING([--enable-floppy], [enable legacy floppy support @<:@default=disabled@:>@]),
+       [], [enable_floppy=no])
+AM_CONDITIONAL([ENABLE_FLOPPY], [test "x$enable_floppy" = "xyes"])
+
+my_CFLAGS="-Wall \
+-Wmissing-declarations -Wmissing-prototypes \
+-Wnested-externs -Wpointer-arith \
+-Wpointer-arith -Wsign-compare -Wchar-subscripts \
+-Wstrict-prototypes -Wshadow \
+-Wformat-security -Wtype-limits"
+AC_SUBST([my_CFLAGS])
+
+AC_CONFIG_HEADERS(config.h)
+AC_CONFIG_FILES([
+       Makefile
+       src/docs/Makefile
+       src/docs/version.xml
+       src/gudev/docs/Makefile
+       src/gudev/docs/version.xml
+])
+
+AC_OUTPUT
+AC_MSG_RESULT([
+        $PACKAGE $VERSION
+        ========
+
+        prefix:                  ${prefix}
+        rootprefix:              ${rootprefix}
+        sysconfdir:              ${sysconfdir}
+        bindir:                  ${bindir}
+        libdir:                  ${libdir}
+        rootlibdir:              ${rootlib_execdir}
+        libexecdir:              ${libexecdir}
+        datarootdir:             ${datarootdir}
+        mandir:                  ${mandir}
+        includedir:              ${includedir}
+        include_prefix:          ${INCLUDE_PREFIX}
+        systemdsystemunitdir:    ${systemdsystemunitdir}
+        firmware path:           ${FIRMWARE_PATH}
+        usb.ids:                 ${USB_DATABASE}
+        pci.ids:                 ${PCI_DATABASE}
+
+        compiler:                ${CC}
+        cflags:                  ${CFLAGS}
+        ldflags:                 ${LDFLAGS}
+        xsltproc:                ${XSLTPROC}
+        gperf:                   ${GPERF}
+
+        logging:                 ${enable_logging}
+        debug:                   ${enable_debug}
+        selinux:                 ${with_selinux}
+
+        man pages                ${enable_manpages}
+        gudev:                   ${enable_gudev}
+        gintrospection:          ${enable_introspection}
+        keymap:                  ${enable_keymap}
+        mtd_probe:               ${enable_mtd_probe}
+        rule_generator:          ${enable_rule_generator}
+        floppy:                  ${enable_floppy}
+])
diff --git a/etc/init.d/S12udev b/etc/init.d/S12udev
deleted file mode 100755
index ed3b959..0000000
--- a/etc/init.d/S12udev
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/bin/sh
-#
-# udev	This is a minimal non-LSB version of a UDEV startup script.  It
-#	was derived by stripping down the udev-058 LSB version for use
-#	with buildroot on embedded hardware using Linux 2.6.34+ kernels.
-#
-#	You may need to customize this for your system's resource limits
-#	(including startup time!) and administration.  For example, if
-#	your early userspace has a custom initramfs or initrd you might
-#	need /dev much earlier; or without hotpluggable busses (like USB,
-#	PCMCIA, MMC/SD, and so on) your /dev might be static after boot.
-#
-#	This script assumes your system boots right into the eventual root
-#	filesystem, and that init runs this udev script before any programs
-#	needing more device nodes than the bare-bones set -- /dev/console,
-#	/dev/zero, /dev/null -- that's needed to boot and run this script.
-#
-
-# Check for missing binaries
-
-if [ -f /etc/ambarella.conf ]
-then
-  . /etc/ambarella.conf
-fi
-
-UDEV_BIN=/lib/udev/udevd
-UDEV_ADM=/usr/sbin/udevadm
-UDEV_MAX_CHILDREN=4
-test -x $UDEV_BIN || exit 5
-
-# Check for config file and read it
-UDEV_CONFIG=/etc/udev/udev.conf
-test -r $UDEV_CONFIG || exit 6
-. $UDEV_CONFIG
-
-case "$1" in
-    start)
-        echo -n "Populating $SYS_BOARD_BSP using udev: "
-        echo -e '\000\000\000\000' > /proc/sys/kernel/hotplug
-        $UDEV_BIN --daemon --children-max=$UDEV_MAX_CHILDREN
-        if [ $? -eq 0 ]
-        then
-        	$UDEV_ADM trigger --subsystem-nomatch=i2c* --type=subsystems --action=add
-        	$UDEV_ADM trigger --subsystem-nomatch=i2c* --type=devices --action=add
-        	$UDEV_ADM trigger --subsystem-match=i2c-* --type=devices --action=add
-        	if [ $? -eq 0 ]
-        	then
-        		echo "Done"
-        	else
-        		echo "Failed"
-        	fi
-        else
-        	echo "Failed"
-        fi
-        ;;
-    stop)
-        # Stop execution of events
-        $UDEV_ADM control --stop_exec_queue
-        killall udevd
-        ;;
-    *)
-        echo "Usage: $0 {start|stop}"
-        exit 1
-        ;;
-esac
-
-exit 0
diff --git a/etc/udev/rules.d/10-drivers.rules b/etc/udev/rules.d/10-drivers.rules
deleted file mode 100644
index 4831dfc..0000000
--- a/etc/udev/rules.d/10-drivers.rules
+++ /dev/null
@@ -1,9 +0,0 @@
-# do not edit this file, it will be overwritten on update
-
-ACTION=="remove", GOTO="drivers_end"
-
-SUBSYSTEM=="platform", KERNELS=="ambarella-sd*", RUN+="/sbin/modprobe -b ambarella_sd"
-SUBSYSTEM=="mmc_host", KERNELS=="mmc[0-9]", RUN+="/sbin/modprobe -b mmc_block"
-SUBSYSTEM=="platform", KERNELS=="ambarella-input*", RUN+="/sbin/modprobe -b ambarella_general_input"
-
-LABEL="drivers_end"
diff --git a/etc/udev/rules.d/11-sd-cards-auto-mount.rules b/etc/udev/rules.d/11-sd-cards-auto-mount.rules
deleted file mode 100644
index 4b5090a..0000000
--- a/etc/udev/rules.d/11-sd-cards-auto-mount.rules
+++ /dev/null
@@ -1,13 +0,0 @@
-KERNEL!="mmcblk[0-9]p[0-9]", GOTO="sd_cards_auto_mount_end"
-
-# Global mount options
-ACTION=="add", ENV{mount_options}="relatime,noatime,nodiratime"
-
-# Filesystem specific options
-ACTION=="add", IMPORT{program}="/sbin/blkid -o udev -p %N"
-ACTION=="add", ENV{ID_FS_TYPE}=="vfat|ntfs", ENV{mount_options}="$env{mount_options},utf8,gid=100,umask=002"
-
-ACTION=="add", RUN+="/bin/mkdir -p /media/%k", RUN+="/bin/ln -s /media/%k /tmp/%k", RUN+="/bin/mount -o $env{mount_options} /dev/%k /media/%k"
-ACTION=="remove", RUN+="/bin/umount -l /media/%k", RUN+="/bin/rm -rf /tmp/%k", RUN+="/bin/rmdir /media/%k"
-LABEL="sd_cards_auto_mount_end"
-
diff --git a/etc/udev/udev.conf b/etc/udev/udev.conf
deleted file mode 100644
index 31bb662..0000000
--- a/etc/udev/udev.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-# The initial syslog(3) priority: "err", "info", "debug" or its
-# numerical equivalent. For runtime debugging, the daemons internal
-# state can be changed with: "udevadm control --log-priority=<value>".
-udev_log="err"
diff --git a/lib/udev/accelerometer b/lib/udev/accelerometer
deleted file mode 100755
index 0839543..0000000
--- a/lib/udev/accelerometer
+++ /dev/null
Binary files differ
diff --git a/lib/udev/ata_id b/lib/udev/ata_id
deleted file mode 100755
index d9baf0b..0000000
--- a/lib/udev/ata_id
+++ /dev/null
Binary files differ
diff --git a/lib/udev/cdrom_id b/lib/udev/cdrom_id
deleted file mode 100755
index 03b4172..0000000
--- a/lib/udev/cdrom_id
+++ /dev/null
Binary files differ
diff --git a/lib/udev/collect b/lib/udev/collect
deleted file mode 100755
index be3995f..0000000
--- a/lib/udev/collect
+++ /dev/null
Binary files differ
diff --git a/lib/udev/findkeyboards b/lib/udev/findkeyboards
deleted file mode 100755
index 537d163..0000000
--- a/lib/udev/findkeyboards
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh -e
-# Find "real" keyboard devices and print their device path.
-# Author: Martin Pitt <martin.pitt@ubuntu.com>
-#
-# Copyright (C) 2009, Canonical Ltd.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-
-# returns OK if $1 contains $2
-strstr() {
-    [ "${1#*$2*}" != "$1" ]
-}
-
-# returns OK if $1 contains $2 at the beginning
-str_starts() {
-    [ "${1#$2*}" != "$1" ]
-}
-
-str_line_starts() {
-    while read a; do str_starts "$a" "$1" && return 0;done
-    return 1;
-}
-
-# print a list of input devices which are keyboard-like
-keyboard_devices() {
-    # standard AT keyboard
-    for dev in `udevadm trigger --dry-run --verbose --property-match=ID_INPUT_KEYBOARD=1`; do
-        walk=`udevadm info --attribute-walk --path=$dev`
-        env=`udevadm info --query=env --path=$dev`
-        # filter out non-event devices, such as the parent input devices which
-        # have no devnode
-        if ! echo "$env" | str_line_starts 'DEVNAME='; then
-            continue
-        fi
-        if strstr "$walk" 'DRIVERS=="atkbd"'; then
-            echo -n 'AT keyboard: '
-        elif echo "$env" | str_line_starts 'ID_USB_DRIVER=usbhid'; then
-            echo -n 'USB keyboard: '
-        else
-            echo -n 'Unknown type: '
-        fi
-        udevadm info --query=name --path=$dev
-    done
-
-    # modules
-    module=$(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*Extra Buttons')
-    module="$module
-$(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*extra buttons')"
-    module="$module
-$(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='Sony Vaio Keys')"
-    for m in $module; do
-        for evdev in $m/event*/dev; do
-            if [ -e "$evdev" ]; then
-		echo -n 'module: '
-		udevadm info --query=name --path=${evdev%%/dev}
-            fi
-	done
-    done
-}
-
-keyboard_devices
diff --git a/lib/udev/firmware b/lib/udev/firmware
deleted file mode 100755
index 962416a..0000000
--- a/lib/udev/firmware
+++ /dev/null
Binary files differ
diff --git a/lib/udev/keyboard-force-release.sh b/lib/udev/keyboard-force-release.sh
deleted file mode 100755
index b157ac3..0000000
--- a/lib/udev/keyboard-force-release.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/sh -e
-# read list of scancodes, convert hex to decimal and
-# append to the atkbd force_release sysfs attribute
-# $1 sysfs devpath for serioX
-# $2 file with scancode list (hex or dec)
-
-case "$2" in
-	/*) scf="$2" ;;
-	*)  scf="/lib/udev/keymaps/force-release/$2" ;;
-esac
-
-read attr <"/sys/$1/force_release"
-while read scancode dummy; do
-	case "$scancode" in
-		\#*) ;;
-		*)
-			scancode=$(($scancode))
-			attr="$attr${attr:+,}$scancode"
-			;;
-	esac
-done <"$scf"
-echo "$attr" >"/sys/$1/force_release"
diff --git a/lib/udev/keymap b/lib/udev/keymap
deleted file mode 100755
index 897a65a..0000000
--- a/lib/udev/keymap
+++ /dev/null
Binary files differ
diff --git a/lib/udev/keymaps/lenovo-ideapad b/lib/udev/keymaps/lenovo-ideapad
deleted file mode 100644
index d5f2567..0000000
--- a/lib/udev/keymaps/lenovo-ideapad
+++ /dev/null
@@ -1,7 +0,0 @@
-# Key codes observed on S10-3, assumed valid on other IdeaPad models
-0x81 rfkill			# does nothing in BIOS
-0x83 display_off		# BIOS toggles screen state
-0xB9 brightnessup		# does nothing in BIOS
-0xBA brightnessdown		# does nothing in BIOS
-0xF1 camera			# BIOS toggles camera power
-0xf2 unknown			# trackpad enable/disable (does nothing in BIOS)
diff --git a/lib/udev/mtd_probe b/lib/udev/mtd_probe
deleted file mode 100755
index 4916d7e..0000000
--- a/lib/udev/mtd_probe
+++ /dev/null
Binary files differ
diff --git a/lib/udev/pci-db b/lib/udev/pci-db
deleted file mode 100755
index 23d94bf..0000000
--- a/lib/udev/pci-db
+++ /dev/null
Binary files differ
diff --git a/lib/udev/rule_generator.functions b/lib/udev/rule_generator.functions
deleted file mode 100644
index 4bec27a..0000000
--- a/lib/udev/rule_generator.functions
+++ /dev/null
@@ -1,116 +0,0 @@
-# functions used by the udev rule generator
-
-# Copyright (C) 2006 Marco d'Itri <md@Linux.IT>
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-PATH='/sbin:/bin'
-#
-
-PATH='/sbin:/bin'
-
-# Read a single line from file $1 in the $DEVPATH directory.
-# The function must not return an error even if the file does not exist.
-sysread() {
-	local file="$1"
-	[ -e "/sys$DEVPATH/$file" ] || return 0
-	local value
-	read value < "/sys$DEVPATH/$file" || return 0
-	echo "$value"
-}
-
-sysreadlink() {
-	local file="$1"
-	[ -e "/sys$DEVPATH/$file" ] || return 0
-	readlink -f /sys$DEVPATH/$file 2> /dev/null || true
-}
-
-# Return true if a directory is writeable.
-writeable() {
-	if ln -s test-link $1/.is-writeable 2> /dev/null; then
-		rm -f $1/.is-writeable
-		return 0
-	else
-		return 1
-	fi
-}
-
-# Create a lock file for the current rules file.
-lock_rules_file() {
-	RUNDIR=$(udevadm info --run)
-	[ -e "$RUNDIR" ] || return 0
-
-	RULES_LOCK="$RUNDIR/.lock-${RULES_FILE##*/}"
-
-	retry=30
-	while ! mkdir $RULES_LOCK 2> /dev/null; do
-		if [ $retry -eq 0 ]; then
-			 echo "Cannot lock $RULES_FILE!" >&2
-			 exit 2
-		fi
-		sleep 1
-		retry=$(($retry - 1))
-	done
-}
-
-unlock_rules_file() {
-	[ "$RULES_LOCK" ] || return 0
-	rmdir $RULES_LOCK || true
-}
-
-# Choose the real rules file if it is writeable or a temporary file if not.
-# Both files should be checked later when looking for existing rules.
-choose_rules_file() {
-	RUNDIR=$(udevadm info --run)
-	local tmp_rules_file="$RUNDIR/tmp-rules--${RULES_FILE##*/}"
-	[ -e "$RULES_FILE" -o -e "$tmp_rules_file" ] || PRINT_HEADER=1
-
-	if writeable ${RULES_FILE%/*}; then
-		RO_RULES_FILE='/dev/null'
-	else
-		RO_RULES_FILE=$RULES_FILE
-		RULES_FILE=$tmp_rules_file
-	fi
-}
-
-# Return the name of the first free device.
-raw_find_next_available() {
-	local links="$1"
-
-	local basename=${links%%[ 0-9]*}
-	local max=-1
-	for name in $links; do
-		local num=${name#$basename}
-		[ "$num" ] || num=0
-		[ $num -gt $max ] && max=$num
-	done
-
-	local max=$(($max + 1))
-	# "name0" actually is just "name"
-	[ $max -eq 0 ] && return
-	echo "$max"
-}
-
-# Find all rules matching a key (with action) and a pattern.
-find_all_rules() {
-	local key="$1"
-	local linkre="$2"
-	local match="$3"
-
-	local search='.*[[:space:],]'"$key"'"('"$linkre"')".*'
-	echo $(sed -n -r -e 's/^#.*//' -e "${match}s/${search}/\1/p" \
-		$RO_RULES_FILE \
-		$([ -e $RULES_FILE ] && echo $RULES_FILE) \
-		2>/dev/null)
-}
diff --git a/lib/udev/rules.d/42-qemu-usb.rules b/lib/udev/rules.d/42-qemu-usb.rules
deleted file mode 100644
index a4e3864..0000000
--- a/lib/udev/rules.d/42-qemu-usb.rules
+++ /dev/null
@@ -1,13 +0,0 @@
-#
-# Enable autosuspend for qemu emulated usb hid devices.
-#
-# Note that there are buggy qemu versions which advertise remote
-# wakeup support but don't actually implement it correctly.  This
-# is the reason why we need a match for the serial number here.
-# The serial number "42" is used to tag the implementations where
-# remote wakeup is working.
-#
-
-ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Mouse", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
-ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Tablet", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
-ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Keyboard", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
diff --git a/lib/udev/rules.d/50-firmware.rules b/lib/udev/rules.d/50-firmware.rules
deleted file mode 100644
index a193adb..0000000
--- a/lib/udev/rules.d/50-firmware.rules
+++ /dev/null
@@ -1,4 +0,0 @@
-# do not edit this file, it will be overwritten on update
-
-# firmware-class requests, copies files into the kernel
-SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware --firmware=$env{FIRMWARE} --devpath=$env{DEVPATH}"
diff --git a/lib/udev/rules.d/75-probe_mtd.rules b/lib/udev/rules.d/75-probe_mtd.rules
deleted file mode 100644
index 184fda5..0000000
--- a/lib/udev/rules.d/75-probe_mtd.rules
+++ /dev/null
@@ -1,8 +0,0 @@
-# do not edit this file, it will be overwritten on update
-
-ACTION!="add", GOTO="mtd_probe_end"
-
-KERNEL=="mtd*ro", IMPORT{program}="mtd_probe $tempnode"
-KERNEL=="mtd*ro", ENV{MTD_FTL}=="smartmedia", RUN+="/sbin/modprobe -bv sm_ftl"
-
-LABEL="mtd_probe_end"
diff --git a/lib/udev/rules.d/80-drivers.rules b/lib/udev/rules.d/80-drivers.rules
deleted file mode 100644
index cf89735..0000000
--- a/lib/udev/rules.d/80-drivers.rules
+++ /dev/null
@@ -1,12 +0,0 @@
-# do not edit this file, it will be overwritten on update
-
-ACTION=="remove", GOTO="drivers_end"
-
-DRIVER!="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe -bv $env{MODALIAS}"
-SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN+="/sbin/modprobe -bv tifm_sd"
-SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN+="/sbin/modprobe -bv tifm_ms"
-SUBSYSTEM=="memstick", RUN+="/sbin/modprobe -bv --all ms_block mspro_block"
-SUBSYSTEM=="i2o", RUN+="/sbin/modprobe -bv i2o_block"
-SUBSYSTEM=="module", KERNEL=="parport_pc", RUN+="/sbin/modprobe -bv ppdev"
-
-LABEL="drivers_end"
diff --git a/lib/udev/scsi_id b/lib/udev/scsi_id
deleted file mode 100755
index 9ffcdc3..0000000
--- a/lib/udev/scsi_id
+++ /dev/null
Binary files differ
diff --git a/lib/udev/udevd b/lib/udev/udevd
deleted file mode 100755
index 2110303..0000000
--- a/lib/udev/udevd
+++ /dev/null
Binary files differ
diff --git a/lib/udev/usb-db b/lib/udev/usb-db
deleted file mode 100755
index 9bd66c1..0000000
--- a/lib/udev/usb-db
+++ /dev/null
Binary files differ
diff --git a/lib/udev/v4l_id b/lib/udev/v4l_id
deleted file mode 100755
index 3137f3d..0000000
--- a/lib/udev/v4l_id
+++ /dev/null
Binary files differ
diff --git a/lib/udev/write_cd_rules b/lib/udev/write_cd_rules
deleted file mode 100755
index 00382ad..0000000
--- a/lib/udev/write_cd_rules
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/bin/sh -e
-
-# This script is run if an optical drive lacks a rule for persistent naming.
-#
-# It adds symlinks for optical drives based on the device class determined
-# by cdrom_id and used ID_PATH to identify the device.
-
-# (C) 2006 Marco d'Itri <md@Linux.IT>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# debug, if UDEV_LOG=<debug>
-if [ -n "$UDEV_LOG" ]; then
-	if [ "$UDEV_LOG" -ge 7 ]; then
-		set -x
-	fi
-fi
-
-RULES_FILE="/etc/udev/rules.d/70-persistent-cd.rules"
-
-. /lib/udev/rule_generator.functions
-
-find_next_available() {
-	raw_find_next_available "$(find_all_rules 'SYMLINK\+=' "$1")"
-}
-
-write_rule() {
-	local match="$1"
-	local link="$2"
-	local comment="$3"
-
-	{
-	if [ "$PRINT_HEADER" ]; then
-		PRINT_HEADER=
-		echo "# This file was automatically generated by the $0"
-		echo "# program, run by the cd-aliases-generator.rules rules file."
-		echo "#"
-		echo "# You can modify it, as long as you keep each rule on a single"
-		echo "# line, and set the \$GENERATED variable."
-		echo ""
-	fi
-
-	[ "$comment" ] && echo "# $comment"
-	echo "$match, SYMLINK+=\"$link\", ENV{GENERATED}=\"1\""
-	} >> $RULES_FILE
-	SYMLINKS="$SYMLINKS $link"
-}
-
-if [ -z "$DEVPATH" ]; then
-	echo "Missing \$DEVPATH." >&2
-	exit 1
-fi
-if [ -z "$ID_CDROM" ]; then
-	echo "$DEVPATH is not a CD reader." >&2
-	exit 1
-fi
-
-if [ "$1" ]; then
-	METHOD="$1"
-else
-	METHOD='by-path'
-fi
-
-case "$METHOD" in
-	by-path)
-	if [ -z "$ID_PATH" ]; then
-		echo "$DEVPATH not supported by path_id. by-id may work." >&2
-		exit 1
-	fi
-	RULE="ENV{ID_PATH}==\"$ID_PATH\""
-	;;
-
-	by-id)
-	if [ "$ID_SERIAL" ]; then
-		RULE="ENV{ID_SERIAL}==\"$ID_SERIAL\""
-	elif [ "$ID_MODEL" -a "$ID_REVISION" ]; then
-		RULE="ENV{ID_MODEL}==\"$ID_MODEL\", ENV{ID_REVISION}==\"$ID_REVISION\""
-	else
-		echo "$DEVPATH not supported by ata_id. by-path may work." >&2
-		exit 1
-	fi
-	;;
-
-	*)
-	echo "Invalid argument (must be either by-path or by-id)." >&2
-	exit 1
-	;;
-esac
-
-# Prevent concurrent processes from modifying the file at the same time.
-lock_rules_file
-
-# Check if the rules file is writeable.
-choose_rules_file
-
-link_num=$(find_next_available 'cdrom[0-9]*')
-
-match="SUBSYSTEM==\"block\", ENV{ID_CDROM}==\"?*\", $RULE"
-
-comment="$ID_MODEL ($ID_PATH)"
-
-	write_rule "$match" "cdrom$link_num" "$comment"
-[ "$ID_CDROM_CD_R" -o "$ID_CDROM_CD_RW" ] && \
-	write_rule "$match" "cdrw$link_num"
-[ "$ID_CDROM_DVD" ] && \
-	write_rule "$match" "dvd$link_num"
-[ "$ID_CDROM_DVD_R" -o "$ID_CDROM_DVD_RW" -o "$ID_CDROM_DVD_RAM" ] && \
-	write_rule "$match" "dvdrw$link_num"
-echo >> $RULES_FILE
-
-unlock_rules_file
-
-echo $SYMLINKS
-
-exit 0
-
diff --git a/lib/udev/write_net_rules b/lib/udev/write_net_rules
deleted file mode 100755
index 4379792..0000000
--- a/lib/udev/write_net_rules
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/bin/sh -e
-
-# This script is run to create persistent network device naming rules
-# based on properties of the device.
-# If the interface needs to be renamed, INTERFACE_NEW=<name> will be printed
-# on stdout to allow udev to IMPORT it.
-
-# variables used to communicate:
-#   MATCHADDR             MAC address used for the match
-#   MATCHID               bus_id used for the match
-#   MATCHDEVID            dev_id used for the match
-#   MATCHDRV              driver name used for the match
-#   MATCHIFTYPE           interface type match
-#   COMMENT               comment to add to the generated rule
-#   INTERFACE_NAME        requested name supplied by external tool
-#   INTERFACE_NEW         new interface name returned by rule writer
-
-# Copyright (C) 2006 Marco d'Itri <md@Linux.IT>
-# Copyright (C) 2007 Kay Sievers <kay.sievers@vrfy.org>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# debug, if UDEV_LOG=<debug>
-if [ -n "$UDEV_LOG" ]; then
-	if [ "$UDEV_LOG" -ge 7 ]; then
-		set -x
-	fi
-fi
-
-RULES_FILE='/etc/udev/rules.d/70-persistent-net.rules'
-
-. /lib/udev/rule_generator.functions
-
-interface_name_taken() {
-	local value="$(find_all_rules 'NAME=' $INTERFACE)"
-	if [ "$value" ]; then
-		return 0
-	else
-		return 1
-	fi
-}
-
-find_next_available() {
-	raw_find_next_available "$(find_all_rules 'NAME=' "$1")"
-}
-
-write_rule() {
-	local match="$1"
-	local name="$2"
-	local comment="$3"
-
-	{
-	if [ "$PRINT_HEADER" ]; then
-		PRINT_HEADER=
-		echo "# This file was automatically generated by the $0"
-		echo "# program, run by the persistent-net-generator.rules rules file."
-		echo "#"
-		echo "# You can modify it, as long as you keep each rule on a single"
-		echo "# line, and change only the value of the NAME= key."
-	fi
-
-	echo ""
-	[ "$comment" ] && echo "# $comment"
-	echo "SUBSYSTEM==\"net\", ACTION==\"add\"$match, NAME=\"$name\""
-	} >> $RULES_FILE
-}
-
-if [ -z "$INTERFACE" ]; then
-	echo "missing \$INTERFACE" >&2
-	exit 1
-fi
-
-# Prevent concurrent processes from modifying the file at the same time.
-lock_rules_file
-
-# Check if the rules file is writeable.
-choose_rules_file
-
-# the DRIVERS key is needed to not match bridges and VLAN sub-interfaces
-if [ "$MATCHADDR" ]; then
-	match="$match, DRIVERS==\"?*\", ATTR{address}==\"$MATCHADDR\""
-fi
-
-if [ "$MATCHDRV" ]; then
-	match="$match, DRIVERS==\"$MATCHDRV\""
-fi
-
-if [ "$MATCHDEVID" ]; then
-	match="$match, ATTR{dev_id}==\"$MATCHDEVID\""
-fi
-
-if [ "$MATCHID" ]; then
-	match="$match, KERNELS==\"$MATCHID\""
-fi
-
-if [ "$MATCHIFTYPE" ]; then
-	match="$match, ATTR{type}==\"$MATCHIFTYPE\""
-fi
-
-if [ -z "$match" ]; then
-	echo "missing valid match" >&2
-	unlock_rules_file
-	exit 1
-fi
-
-basename=${INTERFACE%%[0-9]*}
-match="$match, KERNEL==\"$basename*\""
-
-if [ "$INTERFACE_NAME" ]; then
-	# external tools may request a custom name
-	COMMENT="$COMMENT (custom name provided by external tool)"
-	if [ "$INTERFACE_NAME" != "$INTERFACE" ]; then
-		INTERFACE=$INTERFACE_NAME;
-		echo "INTERFACE_NEW=$INTERFACE"
-	fi
-else
-	# if a rule using the current name already exists, find a new name
-	if interface_name_taken; then
-		INTERFACE="$basename$(find_next_available "$basename[0-9]*")"
-		# prevent INTERFACE from being "eth" instead of "eth0"
-		[ "$INTERFACE" = "${INTERFACE%%[ \[\]0-9]*}" ] && INTERFACE=${INTERFACE}0
-		echo "INTERFACE_NEW=$INTERFACE"
-	fi
-fi
-
-write_rule "$match" "$INTERFACE" "$COMMENT"
-
-unlock_rules_file
-
-exit 0
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 0000000..0ca2c03
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,4 @@
+libtool.m4
+lt*m4
+gtk-doc.m4
+
diff --git a/rules/42-usb-hid-pm.rules b/rules/42-usb-hid-pm.rules
new file mode 100644
index 0000000..d5d5897
--- /dev/null
+++ b/rules/42-usb-hid-pm.rules
@@ -0,0 +1,49 @@
+#
+# Enable autosuspend for qemu emulated usb hid devices.
+#
+# Note that there are buggy qemu versions which advertise remote
+# wakeup support but don't actually implement it correctly.  This
+# is the reason why we need a match for the serial number here.
+# The serial number "42" is used to tag the implementations where
+# remote wakeup is working.
+#
+
+ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Mouse", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Tablet", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Keyboard", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
+
+#
+# Enable autosuspend for KVM and iLO usb hid devices. These are
+# effectively self-powered (despite what some claim in their USB
+# profiles) and so it's safe to do so.
+#
+
+# AMI 046b:ff10
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="046b", ATTR{idProduct}=="ff10", TEST=="power/control", ATTR{power/control}="auto"
+
+#
+# Catch-all for Avocent HID devices. Keyed off interface in order to only
+# trigger on HID class devices.
+#
+ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="0624", ATTR{bInterfaceClass}=="03", TEST=="../power/control", ATTR{../power/control}="auto"
+
+# Dell DRAC 4
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="413c", ATTR{idProduct}=="2500", TEST=="power/control", ATTR{power/control}="auto"
+
+# Dell DRAC 5
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="413c", ATTR{idProduct}=="0000", TEST=="power/control", ATTR{power/control}="auto"
+
+# Hewlett Packard iLO
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="03f0", ATTR{idProduct}=="7029", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="03f0", ATTR{idProduct}=="1027", TEST=="power/control", ATTR{power/control}="auto"
+
+# IBM remote access
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="04b3", ATTR{idProduct}=="4001", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="04b3", ATTR{idProduct}=="4002", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="04b3", ATTR{idProduct}=="4012", TEST=="power/control", ATTR{power/control}="auto"
+
+# Raritan Computer, Inc KVM.
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="14dd", ATTR{idProduct}="0002", TEST=="power/control", ATTR{power/control}="auto"
+
+# USB HID devices that are internal to the machine should also be safe to autosuspend
+ACTION=="add", SUBSYSTEM=="usb", ATTR{bInterfaceClass}=="03", ATTRS{removable}=="fixed", TEST=="../power/control", ATTR{../power/control}="auto"
diff --git a/lib/udev/rules.d/50-udev-default.rules b/rules/50-udev-default.rules
similarity index 63%
rename from lib/udev/rules.d/50-udev-default.rules
rename to rules/50-udev-default.rules
index c47cdec..5ad787f 100644
--- a/lib/udev/rules.d/50-udev-default.rules
+++ b/rules/50-udev-default.rules
@@ -2,44 +2,44 @@
 
 KERNEL=="pty[pqrstuvwxyzabcdef][0123456789abcdef]", GROUP="tty", MODE="0660"
 KERNEL=="tty[pqrstuvwxyzabcdef][0123456789abcdef]", GROUP="tty", MODE="0660"
-KERNEL=="ptmx",			GROUP="tty", MODE="0666"
-KERNEL=="tty",			GROUP="tty", MODE="0666"
-KERNEL=="tty[0-9]*",		GROUP="tty", MODE="0620"
+KERNEL=="ptmx", GROUP="tty", MODE="0666"
+KERNEL=="tty", GROUP="tty", MODE="0666"
+KERNEL=="tty[0-9]*", GROUP="tty", MODE="0620"
 KERNEL=="vcs|vcs[0-9]*|vcsa|vcsa[0-9]*", GROUP="tty"
 
 # serial
 KERNEL=="tty[A-Z]*[0-9]|pppox[0-9]*|ircomm[0-9]*|noz[0-9]*|rfcomm[0-9]*", GROUP="dialout"
-KERNEL=="mwave",		GROUP="dialout"
-KERNEL=="hvc*|hvsi*",		GROUP="dialout"
+KERNEL=="mwave", GROUP="dialout"
+KERNEL=="hvc*|hvsi*", GROUP="dialout"
 
 # virtio serial / console ports
 KERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name}"
 
 # mem
 KERNEL=="null|zero|full|random|urandom", MODE="0666"
-KERNEL=="mem|kmem|port|nvram",	GROUP="kmem", MODE="0640"
+KERNEL=="mem|kmem|port|nvram", GROUP="kmem", MODE="0640"
 
 # input
 SUBSYSTEM=="input", ENV{ID_INPUT}=="", IMPORT{builtin}="input_id"
-KERNEL=="mouse*|mice|event*",	MODE="0640"
-KERNEL=="ts[0-9]*|uinput",	MODE="0640"
-KERNEL=="js[0-9]*",		MODE="0644"
+KERNEL=="mouse*|mice|event*", MODE="0640"
+KERNEL=="ts[0-9]*|uinput", MODE="0640"
+KERNEL=="js[0-9]*", MODE="0644"
 
 # video4linux
-SUBSYSTEM=="video4linux",	GROUP="video"
-KERNEL=="vttuner*",		GROUP="video"
-KERNEL=="vtx*|vbi*",		GROUP="video"
-KERNEL=="winradio*",		GROUP="video"
+SUBSYSTEM=="video4linux", GROUP="video"
+KERNEL=="vttuner*", GROUP="video"
+KERNEL=="vtx*|vbi*", GROUP="video"
+KERNEL=="winradio*", GROUP="video"
 
 # graphics
-KERNEL=="agpgart",		GROUP="video"
-KERNEL=="pmu",			GROUP="video"
-KERNEL=="nvidia*|nvidiactl*",	GROUP="video"
-SUBSYSTEM=="graphics",		GROUP="video"
-SUBSYSTEM=="drm",		GROUP="video"
+KERNEL=="agpgart", GROUP="video"
+KERNEL=="pmu", GROUP="video"
+KERNEL=="nvidia*|nvidiactl*", GROUP="video"
+SUBSYSTEM=="graphics", GROUP="video"
+SUBSYSTEM=="drm", GROUP="video"
 
 # sound
-SUBSYSTEM=="sound",		GROUP="audio", \
+SUBSYSTEM=="sound", GROUP="audio", \
   OPTIONS+="static_node=snd/seq", OPTIONS+="static_node=snd/timer"
 
 # DVB (video)
@@ -56,11 +56,11 @@
 SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id"
 
 # printer
-KERNEL=="parport[0-9]*",	GROUP="lp"
-SUBSYSTEM=="printer",		KERNEL=="lp*", GROUP="lp"
-SUBSYSTEM=="ppdev",		GROUP="lp"
-KERNEL=="lp[0-9]*",		GROUP="lp"
-KERNEL=="irlpt[0-9]*",		GROUP="lp"
+KERNEL=="parport[0-9]*", GROUP="lp"
+SUBSYSTEM=="printer", KERNEL=="lp*", GROUP="lp"
+SUBSYSTEM=="ppdev", GROUP="lp"
+KERNEL=="lp[0-9]*", GROUP="lp"
+KERNEL=="irlpt[0-9]*", GROUP="lp"
 SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", GROUP="lp"
 
 # block
@@ -91,15 +91,17 @@
 SUBSYSTEM=="aoe", KERNEL=="err", MODE="0440"
 
 # network
-KERNEL=="tun",			MODE="0666", OPTIONS+="static_node=net/tun"
-KERNEL=="rfkill",		MODE="0644"
+KERNEL=="tun", MODE="0666", OPTIONS+="static_node=net/tun"
+KERNEL=="rfkill", MODE="0644"
 
 # CPU
-KERNEL=="cpu[0-9]*",		MODE="0444"
+KERNEL=="cpu[0-9]*", MODE="0444"
 
-KERNEL=="fuse", ACTION=="add",	MODE="0666", OPTIONS+="static_node=fuse"
+KERNEL=="fuse", ACTION=="add", MODE="0666", OPTIONS+="static_node=fuse"
 
-SUBSYSTEM=="rtc", DRIVERS=="rtc_cmos", SYMLINK+="rtc"
-KERNEL=="mmtimer",		MODE="0644"
-KERNEL=="rflash[0-9]*",		MODE="0400"
-KERNEL=="rrom[0-9]*",		MODE="0400"
+SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
+KERNEL=="mmtimer", MODE="0644"
+KERNEL=="rflash[0-9]*", MODE="0400"
+KERNEL=="rrom[0-9]*", MODE="0400"
+
+SUBSYSTEM=="firmware", ACTION=="add", IMPORT{builtin}="firmware"
diff --git a/lib/udev/rules.d/60-persistent-alsa.rules b/rules/60-persistent-alsa.rules
similarity index 100%
rename from lib/udev/rules.d/60-persistent-alsa.rules
rename to rules/60-persistent-alsa.rules
diff --git a/lib/udev/rules.d/60-persistent-input.rules b/rules/60-persistent-input.rules
similarity index 100%
rename from lib/udev/rules.d/60-persistent-input.rules
rename to rules/60-persistent-input.rules
diff --git a/lib/udev/rules.d/60-persistent-serial.rules b/rules/60-persistent-serial.rules
similarity index 100%
rename from lib/udev/rules.d/60-persistent-serial.rules
rename to rules/60-persistent-serial.rules
diff --git a/lib/udev/rules.d/60-persistent-storage-tape.rules b/rules/60-persistent-storage-tape.rules
similarity index 98%
rename from lib/udev/rules.d/60-persistent-storage-tape.rules
rename to rules/60-persistent-storage-tape.rules
index b1a64ce..f2eabd9 100644
--- a/lib/udev/rules.d/60-persistent-storage-tape.rules
+++ b/rules/60-persistent-storage-tape.rules
@@ -5,7 +5,7 @@
 ACTION=="remove", GOTO="persistent_storage_tape_end"
 
 # type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $tempnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
   SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}"
 
 SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end"
diff --git a/lib/udev/rules.d/60-persistent-storage.rules b/rules/60-persistent-storage.rules
similarity index 83%
rename from lib/udev/rules.d/60-persistent-storage.rules
rename to rules/60-persistent-storage.rules
index 894b50b..b74821e 100644
--- a/lib/udev/rules.d/60-persistent-storage.rules
+++ b/rules/60-persistent-storage.rules
@@ -27,20 +27,20 @@
 KERNEL=="vd*[0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}-part%n"
 
 # ATA devices with their own "ata" kernel subsystem
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="ata", IMPORT{program}="ata_id --export $tempnode"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="ata", IMPORT{program}="ata_id --export $devnode"
 # ATA devices using the "scsi" subsystem
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $tempnode"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
 # ATA/ATAPI devices (SPC-3 or later) using the "scsi" subsystem
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $tempnode"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
 
 # Run ata_id on non-removable USB Mass Storage (SATA/PATA disks in enclosures)
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $tempnode"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $devnode"
 # Otherwise fall back to using usb_id for USB devices
 KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
 
 # scsi devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $tempnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $tempnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
 KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
 KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
 
@@ -48,10 +48,6 @@
 KERNEL=="sd*[!0-9]|sr*", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}"
 KERNEL=="sd*[0-9]", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}-part%n"
 
-# scsi compat links for ATA devices
-KERNEL=="sd*[!0-9]", ENV{ID_BUS}=="ata", PROGRAM="scsi_id --whitelisted --replace-whitespace -p0x80 -d$tempnode", RESULT=="?*", ENV{ID_SCSI_COMPAT}="$result", SYMLINK+="disk/by-id/scsi-$env{ID_SCSI_COMPAT}"
-KERNEL=="sd*[0-9]", ENV{ID_SCSI_COMPAT}=="?*", SYMLINK+="disk/by-id/scsi-$env{ID_SCSI_COMPAT}-part%n"
-
 KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{name}=="?*", ATTRS{serial}=="?*", ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}"
 KERNEL=="mmcblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
 KERNEL=="mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}=="?*", ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}"
@@ -66,12 +62,14 @@
 ENV{DEVTYPE}=="disk", KERNEL!="sd*|sr*", ATTR{removable}=="1", GOTO="persistent_storage_end"
 
 # probe filesystem metadata of optical drives which have a media inserted
-KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="?*", IMPORT{program}="/sbin/blkid -o udev -p -u noraid -O $env{ID_CDROM_MEDIA_SESSION_LAST_OFFSET} $tempnode"
+KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="?*", \
+  IMPORT{builtin}="blkid --offset=$env{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}"
 # single-session CDs do not have ID_CDROM_MEDIA_SESSION_LAST_OFFSET
-KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="", IMPORT{program}="/sbin/blkid -o udev -p -u noraid $tempnode"
+KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="", \
+  IMPORT{builtin}="blkid --noraid"
 
 # probe filesystem metadata of disks
-KERNEL!="sr*", IMPORT{program}="/sbin/blkid -o udev -p $tempnode"
+KERNEL!="sr*", IMPORT{builtin}="blkid"
 
 # watch metadata changes by tools closing the device after writing
 KERNEL!="sr*", OPTIONS+="watch"
diff --git a/lib/udev/rules.d/75-net-description.rules b/rules/75-net-description.rules
similarity index 74%
rename from lib/udev/rules.d/75-net-description.rules
rename to rules/75-net-description.rules
index 0ffce2c..ce57d48 100644
--- a/lib/udev/rules.d/75-net-description.rules
+++ b/rules/75-net-description.rules
@@ -4,11 +4,11 @@
 SUBSYSTEM!="net", GOTO="net_end"
 
 SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id"
-SUBSYSTEMS=="usb", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{program}="usb-db %p"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb-db"
 SUBSYSTEMS=="usb", ATTRS{idVendor}!="", ATTRS{idProduct}!="", ENV{ID_VENDOR_ID}="$attr{idVendor}", ENV{ID_MODEL_ID}="$attr{idProduct}"
 SUBSYSTEMS=="usb", GOTO="net_end"
 
-SUBSYSTEMS=="pci", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{program}="pci-db %p"
+SUBSYSTEMS=="pci", IMPORT{builtin}="pci-db"
 SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
 
 LABEL="net_end"
diff --git a/lib/udev/rules.d/75-tty-description.rules b/rules/75-tty-description.rules
similarity index 74%
rename from lib/udev/rules.d/75-tty-description.rules
rename to rules/75-tty-description.rules
index b67c857..2e63e14 100644
--- a/lib/udev/rules.d/75-tty-description.rules
+++ b/rules/75-tty-description.rules
@@ -4,11 +4,11 @@
 SUBSYSTEM!="tty", GOTO="tty_end"
 
 SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id"
-SUBSYSTEMS=="usb", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{program}="usb-db %p"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb-db"
 SUBSYSTEMS=="usb", ATTRS{idVendor}!="", ATTRS{idProduct}!="", ENV{ID_VENDOR_ID}="$attr{idVendor}", ENV{ID_MODEL_ID}="$attr{idProduct}"
 SUBSYSTEMS=="usb", GOTO="tty_end"
 
-SUBSYSTEMS=="pci", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{program}="pci-db %p"
+SUBSYSTEMS=="pci", IMPORT{builtin}="pci-db"
 SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
 
 LABEL="tty_end"
diff --git a/lib/udev/rules.d/78-sound-card.rules b/rules/78-sound-card.rules
similarity index 91%
rename from lib/udev/rules.d/78-sound-card.rules
rename to rules/78-sound-card.rules
index e3a13b5..e564441 100644
--- a/lib/udev/rules.d/78-sound-card.rules
+++ b/rules/78-sound-card.rules
@@ -38,10 +38,16 @@
 ENV{SOUND_INITIALIZED}="1"
 
 SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
-SUBSYSTEMS=="usb", ENV{ID_VENDOR_FROM_DATABASE}=="", IMPORT{program}="usb-db %p"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb-db"
 SUBSYSTEMS=="usb", GOTO="skip_pci"
 
-SUBSYSTEMS=="pci", ENV{ID_VENDOR_FROM_DATABASE}=="", IMPORT{program}="pci-db %p"
+SUBSYSTEMS=="firewire", ATTRS{vendor_name}=="?*", ATTRS{model_name}=="?*", \
+  ENV{ID_BUS}="firewire", ENV{ID_VENDOR}="$attr{vendor_name}", ENV{ID_MODEL}="$attr{model_name}"
+SUBSYSTEMS=="firewire", ATTRS{guid}=="?*", ENV{ID_ID}="firewire-$attr{guid}"
+SUBSYSTEMS=="firewire", GOTO="skip_pci"
+
+
+SUBSYSTEMS=="pci", IMPORT{builtin}="pci-db"
 SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
 
 LABEL="skip_pci"
diff --git a/rules/80-drivers.rules b/rules/80-drivers.rules
new file mode 100644
index 0000000..38ebfeb
--- /dev/null
+++ b/rules/80-drivers.rules
@@ -0,0 +1,12 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="drivers_end"
+
+DRIVER!="?*", ENV{MODALIAS}=="?*", IMPORT{builtin}="kmod load $env{MODALIAS}"
+SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", IMPORT{builtin}="kmod load tifm_sd"
+SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", IMPORT{builtin}="kmod load tifm_ms"
+SUBSYSTEM=="memstick", IMPORT{builtin}="kmod load ms_block mspro_block"
+SUBSYSTEM=="i2o", IMPORT{builtin}="kmod load i2o_block"
+SUBSYSTEM=="module", KERNEL=="parport_pc", IMPORT{builtin}="kmod load ppdev"
+
+LABEL="drivers_end"
diff --git a/lib/udev/rules.d/95-udev-late.rules b/rules/95-udev-late.rules
similarity index 100%
rename from lib/udev/rules.d/95-udev-late.rules
rename to rules/95-udev-late.rules
diff --git a/run b/run
deleted file mode 120000
index 1c2f433..0000000
--- a/run
+++ /dev/null
@@ -1 +0,0 @@
-tmp
\ No newline at end of file
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..beb8604
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,5 @@
+*.[78]
+*.html
+udev.pc
+libudev.pc
+udev*.service
diff --git a/licenses/libraries/COPYING b/src/COPYING
similarity index 99%
rename from licenses/libraries/COPYING
rename to src/COPYING
index 84bba36..d2e3127 100644
--- a/licenses/libraries/COPYING
+++ b/src/COPYING
@@ -55,7 +55,7 @@
 that what they have is not the original version, so that the original
 author's reputation will not be affected by problems that might be
 introduced by others.
-
+
   Finally, software patents pose a constant threat to the existence of
 any free program.  We wish to make sure that a company cannot
 effectively restrict the users of a free program by obtaining a
@@ -111,7 +111,7 @@
 "work based on the library" and a "work that uses the library".  The
 former contains code derived from the library, whereas the latter must
 be combined with the library in order to run.
-
+
                   GNU LESSER GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
@@ -216,7 +216,7 @@
 ordinary GNU General Public License has appeared, then you can specify
 that version instead if you wish.)  Do not make any other change in
 these notices.
-
+
   Once this change is made in a given copy, it is irreversible for
 that copy, so the ordinary GNU General Public License applies to all
 subsequent copies and derivative works made from that copy.
@@ -267,7 +267,7 @@
 distribute the object code for the work under the terms of Section 6.
 Any executables containing that work also fall under Section 6,
 whether or not they are linked directly with the Library itself.
-
+
   6. As an exception to the Sections above, you may also combine or
 link a "work that uses the Library" with the Library to produce a
 work containing portions of the Library, and distribute that work
@@ -329,7 +329,7 @@
 accompany the operating system.  Such a contradiction means you cannot
 use both them and the Library together in an executable that you
 distribute.
-
+
   7. You may place library facilities that are a work based on the
 Library side-by-side in a single library together with other library
 facilities not covered by this License, and distribute such a combined
@@ -370,7 +370,7 @@
 restrictions on the recipients' exercise of the rights granted herein.
 You are not responsible for enforcing compliance by third parties with
 this License.
-
+
   11. If, as a consequence of a court judgment or allegation of patent
 infringement or for any other reason (not limited to patent issues),
 conditions are imposed on you (whether by court order, agreement or
@@ -422,7 +422,7 @@
 the Free Software Foundation.  If the Library does not specify a
 license version number, you may choose any version ever published by
 the Free Software Foundation.
-
+
   14. If you wish to incorporate parts of the Library into other free
 programs whose distribution conditions are incompatible with these,
 write to the author to ask for permission.  For software which is
@@ -456,7 +456,7 @@
 DAMAGES.
 
                      END OF TERMS AND CONDITIONS
-
+
            How to Apply These Terms to Your New Libraries
 
   If you develop a new library, and you want it to be of the greatest
diff --git a/lib/udev/rules.d/61-accelerometer.rules b/src/accelerometer/61-accelerometer.rules
similarity index 100%
rename from lib/udev/rules.d/61-accelerometer.rules
rename to src/accelerometer/61-accelerometer.rules
diff --git a/src/accelerometer/accelerometer.c b/src/accelerometer/accelerometer.c
new file mode 100644
index 0000000..bc9715b
--- /dev/null
+++ b/src/accelerometer/accelerometer.c
@@ -0,0 +1,357 @@
+/*
+ * accelerometer - exports device orientation through property
+ *
+ * When an "change" event is received on an accelerometer,
+ * open its device node, and from the value, as well as the previous
+ * value of the property, calculate the device's new orientation,
+ * and export it as ID_INPUT_ACCELEROMETER_ORIENTATION.
+ *
+ * Possible values are:
+ * undefined
+ * * normal
+ * * bottom-up
+ * * left-up
+ * * right-up
+ *
+ * The property will be persistent across sessions, and the new
+ * orientations can be deducted from the previous one (it allows
+ * for a threshold for switching between opposite ends of the
+ * orientation).
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author:
+ *   Bastien Nocera <hadess@hadess.net>
+ *
+ * orientation_calc() from the sensorfw package
+ * Copyright (C) 2009-2010 Nokia Corporation
+ * Authors:
+ *   Üstün Ergenoglu <ext-ustun.ergenoglu@nokia.com>
+ *   Timo Rongas <ext-timo.2.rongas@nokia.com>
+ *   Lihan Guo <lihan.guo@digia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with keymap; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/limits.h>
+#include <linux/input.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/* we must use this kernel-compatible implementation */
+#define BITS_PER_LONG (sizeof(unsigned long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x)  ((x)%BITS_PER_LONG)
+#define BIT(x)  (1UL<<OFF(x))
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array)    ((array[LONG(bit)] >> OFF(bit)) & 1)
+
+static int debug = 0;
+
+static void log_fn(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        if (debug) {
+                fprintf(stderr, "%s: ", fn);
+                vfprintf(stderr, format, args);
+        } else {
+                vsyslog(priority, format, args);
+        }
+}
+
+typedef enum {
+        ORIENTATION_UNDEFINED,
+        ORIENTATION_NORMAL,
+        ORIENTATION_BOTTOM_UP,
+        ORIENTATION_LEFT_UP,
+        ORIENTATION_RIGHT_UP
+} OrientationUp;
+
+static const char *orientations[] = {
+        "undefined",
+        "normal",
+        "bottom-up",
+        "left-up",
+        "right-up",
+        NULL
+};
+
+#define ORIENTATION_UP_UP ORIENTATION_NORMAL
+
+#define DEFAULT_THRESHOLD 250
+#define RADIANS_TO_DEGREES 180.0/M_PI
+#define SAME_AXIS_LIMIT 5
+
+#define THRESHOLD_LANDSCAPE  25
+#define THRESHOLD_PORTRAIT  20
+
+static const char *
+orientation_to_string (OrientationUp o)
+{
+        return orientations[o];
+}
+
+static OrientationUp
+string_to_orientation (const char *orientation)
+{
+        int i;
+
+        if (orientation == NULL)
+                return ORIENTATION_UNDEFINED;
+        for (i = 0; orientations[i] != NULL; i++) {
+                if (strcmp (orientation, orientations[i]) == 0)
+                        return i;
+        }
+        return ORIENTATION_UNDEFINED;
+}
+
+static OrientationUp
+orientation_calc (OrientationUp prev,
+                  int x, int y, int z)
+{
+        int rotation;
+        OrientationUp ret = prev;
+
+        /* Portrait check */
+        rotation = round(atan((double) x / sqrt(y * y + z * z)) * RADIANS_TO_DEGREES);
+
+        if (abs(rotation) > THRESHOLD_PORTRAIT) {
+                ret = (rotation < 0) ? ORIENTATION_LEFT_UP : ORIENTATION_RIGHT_UP;
+
+                /* Some threshold to switching between portrait modes */
+                if (prev == ORIENTATION_LEFT_UP || prev == ORIENTATION_RIGHT_UP) {
+                        if (abs(rotation) < SAME_AXIS_LIMIT) {
+                                ret = prev;
+                        }
+                }
+
+        } else {
+                /* Landscape check */
+                rotation = round(atan((double) y / sqrt(x * x + z * z)) * RADIANS_TO_DEGREES);
+
+                if (abs(rotation) > THRESHOLD_LANDSCAPE) {
+                        ret = (rotation < 0) ? ORIENTATION_BOTTOM_UP : ORIENTATION_NORMAL;
+
+                        /* Some threshold to switching between landscape modes */
+                        if (prev == ORIENTATION_BOTTOM_UP || prev == ORIENTATION_NORMAL) {
+                                if (abs(rotation) < SAME_AXIS_LIMIT) {
+                                        ret = prev;
+                                }
+                        }
+                }
+        }
+
+        return ret;
+}
+
+static OrientationUp
+get_prev_orientation(struct udev_device *dev)
+{
+        const char *value;
+
+        value = udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER_ORIENTATION");
+        if (value == NULL)
+                return ORIENTATION_UNDEFINED;
+        return string_to_orientation(value);
+}
+
+#define SET_AXIS(axis, code_) if (ev[i].code == code_) { if (got_##axis == 0) { axis = ev[i].value; got_##axis = 1; } }
+
+/* accelerometers */
+static void test_orientation(struct udev *udev,
+                             struct udev_device *dev,
+                             const char *devpath)
+{
+        OrientationUp old, new;
+        int fd, r;
+        struct input_event ev[64];
+        int got_syn = 0;
+        int got_x, got_y, got_z;
+        int x = 0, y = 0, z = 0;
+        char text[64];
+
+        old = get_prev_orientation(dev);
+
+        if ((fd = open(devpath, O_RDONLY)) < 0)
+                return;
+
+        got_x = got_y = got_z = 0;
+
+        while (1) {
+                int i;
+
+                r = read(fd, ev, sizeof(struct input_event) * 64);
+
+                if (r < (int) sizeof(struct input_event))
+                        return;
+
+                for (i = 0; i < r / (int) sizeof(struct input_event); i++) {
+                        if (got_syn == 1) {
+                                if (ev[i].type == EV_ABS) {
+                                        SET_AXIS(x, ABS_X);
+                                        SET_AXIS(y, ABS_Y);
+                                        SET_AXIS(z, ABS_Z);
+                                }
+                        }
+                        if (ev[i].type == EV_SYN && ev[i].code == SYN_REPORT) {
+                                got_syn = 1;
+                        }
+                        if (got_x && got_y && got_z)
+                                goto read_dev;
+                }
+        }
+
+read_dev:
+        close(fd);
+
+        if (!got_x || !got_y || !got_z)
+                return;
+
+        new = orientation_calc(old, x, y, z);
+        snprintf(text, sizeof(text), "ID_INPUT_ACCELEROMETER_ORIENTATION=%s", orientation_to_string(new));
+        puts(text);
+}
+
+static void help(void)
+{
+        printf("Usage: accelerometer [options] <device path>\n"
+               "  --debug         debug to stderr\n"
+               "  --help          print this help text\n\n");
+}
+
+int main (int argc, char** argv)
+{
+        struct udev *udev;
+        struct udev_device *dev;
+
+        static const struct option options[] = {
+                { "debug", no_argument, NULL, 'd' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+
+        char devpath[PATH_MAX];
+        char *devnode;
+        const char *id_path;
+        struct udev_enumerate *enumerate;
+        struct udev_list_entry *list_entry;
+
+        udev = udev_new();
+        if (udev == NULL)
+                return 1;
+
+        udev_log_init("input_id");
+        udev_set_log_fn(udev, log_fn);
+
+        /* CLI argument parsing */
+        while (1) {
+                int option;
+
+                option = getopt_long(argc, argv, "dxh", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'd':
+                        debug = 1;
+                        if (udev_get_log_priority(udev) < LOG_INFO)
+                                udev_set_log_priority(udev, LOG_INFO);
+                        break;
+                case 'h':
+                        help();
+                        exit(0);
+                default:
+                        exit(1);
+                }
+        }
+
+        if (argv[optind] == NULL) {
+                help();
+                exit(1);
+        }
+
+        /* get the device */
+        snprintf(devpath, sizeof(devpath), "%s/%s", udev_get_sys_path(udev), argv[optind]);
+        dev = udev_device_new_from_syspath(udev, devpath);
+        if (dev == NULL) {
+                fprintf(stderr, "unable to access '%s'\n", devpath);
+                return 1;
+        }
+
+        id_path = udev_device_get_property_value(dev, "ID_PATH");
+        if (id_path == NULL) {
+                fprintf (stderr, "unable to get property ID_PATH for '%s'", devpath);
+                return 0;
+        }
+
+        /* Get the children devices and find the devnode
+         * FIXME: use udev_enumerate_add_match_children() instead
+         * when it's available */
+        devnode = NULL;
+        enumerate = udev_enumerate_new(udev);
+        udev_enumerate_add_match_property(enumerate, "ID_PATH", id_path);
+        udev_enumerate_add_match_subsystem(enumerate, "input");
+        udev_enumerate_scan_devices(enumerate);
+        udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) {
+                struct udev_device *device;
+                const char *node;
+
+                device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate),
+                                                      udev_list_entry_get_name(list_entry));
+                if (device == NULL)
+                        continue;
+                /* Already found it */
+                if (devnode != NULL) {
+                        udev_device_unref(device);
+                        continue;
+                }
+
+                node = udev_device_get_devnode(device);
+                if (node == NULL) {
+                        udev_device_unref(device);
+                        continue;
+                }
+                /* Use the event sub-device */
+                if (strstr(node, "/event") == NULL) {
+                        udev_device_unref(device);
+                        continue;
+                }
+
+                devnode = strdup(node);
+                udev_device_unref(device);
+        }
+
+        if (devnode == NULL) {
+                fprintf(stderr, "unable to get device node for '%s'\n", devpath);
+                return 0;
+        }
+
+        info(udev, "Opening accelerometer device %s\n", devnode);
+        test_orientation(udev, dev, devnode);
+        free(devnode);
+
+        return 0;
+}
diff --git a/src/ata_id/ata_id.c b/src/ata_id/ata_id.c
new file mode 100644
index 0000000..846a73b
--- /dev/null
+++ b/src/ata_id/ata_id.c
@@ -0,0 +1,721 @@
+/*
+ * ata_id - reads product/serial number from ATA drives
+ *
+ * Copyright (C) 2005-2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Lennart Poettering <lennart@poettering.net>
+ * Copyright (C) 2009-2010 David Zeuthen <zeuthen@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <scsi/scsi.h>
+#include <scsi/sg.h>
+#include <scsi/scsi_ioctl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hdreg.h>
+#include <linux/fs.h>
+#include <linux/cdrom.h>
+#include <linux/bsg.h>
+#include <arpa/inet.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+#define COMMAND_TIMEOUT_MSEC (30 * 1000)
+
+static int disk_scsi_inquiry_command(int      fd,
+                                     void    *buf,
+                                     size_t   buf_len)
+{
+        struct sg_io_v4 io_v4;
+        uint8_t cdb[6];
+        uint8_t sense[32];
+        int ret;
+
+        /*
+         * INQUIRY, see SPC-4 section 6.4
+         */
+        memset(cdb, 0, sizeof(cdb));
+        cdb[0] = 0x12;                         /* OPERATION CODE: INQUIRY */
+        cdb[3] = (buf_len >> 8);         /* ALLOCATION LENGTH */
+        cdb[4] = (buf_len & 0xff);
+
+        memset(sense, 0, sizeof(sense));
+
+        memset(&io_v4, 0, sizeof(struct sg_io_v4));
+        io_v4.guard = 'Q';
+        io_v4.protocol = BSG_PROTOCOL_SCSI;
+        io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+        io_v4.request_len = sizeof (cdb);
+        io_v4.request = (uintptr_t) cdb;
+        io_v4.max_response_len = sizeof (sense);
+        io_v4.response = (uintptr_t) sense;
+        io_v4.din_xfer_len = buf_len;
+        io_v4.din_xferp = (uintptr_t) buf;
+        io_v4.timeout = COMMAND_TIMEOUT_MSEC;
+
+        ret = ioctl(fd, SG_IO, &io_v4);
+        if (ret != 0) {
+                /* could be that the driver doesn't do version 4, try version 3 */
+                if (errno == EINVAL) {
+                        struct sg_io_hdr io_hdr;
+
+                        memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+                        io_hdr.interface_id = 'S';
+                        io_hdr.cmdp = (unsigned char*) cdb;
+                        io_hdr.cmd_len = sizeof (cdb);
+                        io_hdr.dxferp = buf;
+                        io_hdr.dxfer_len = buf_len;
+                        io_hdr.sbp = sense;
+                        io_hdr.mx_sb_len = sizeof (sense);
+                        io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+                        io_hdr.timeout = COMMAND_TIMEOUT_MSEC;
+
+                        ret = ioctl(fd, SG_IO, &io_hdr);
+                        if (ret != 0)
+                                goto out;
+
+                        /* even if the ioctl succeeds, we need to check the return value */
+                        if (!(io_hdr.status == 0 &&
+                              io_hdr.host_status == 0 &&
+                              io_hdr.driver_status == 0)) {
+                                errno = EIO;
+                                ret = -1;
+                                goto out;
+                        }
+                } else {
+                        goto out;
+                }
+        }
+
+        /* even if the ioctl succeeds, we need to check the return value */
+        if (!(io_v4.device_status == 0 &&
+              io_v4.transport_status == 0 &&
+              io_v4.driver_status == 0)) {
+                errno = EIO;
+                ret = -1;
+                goto out;
+        }
+
+ out:
+        return ret;
+}
+
+static int disk_identify_command(int          fd,
+                                 void         *buf,
+                                 size_t          buf_len)
+{
+        struct sg_io_v4 io_v4;
+        uint8_t cdb[12];
+        uint8_t sense[32];
+        uint8_t *desc = sense+8;
+        int ret;
+
+        /*
+         * ATA Pass-Through 12 byte command, as described in
+         *
+         *  T10 04-262r8 ATA Command Pass-Through
+         *
+         * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf
+         */
+        memset(cdb, 0, sizeof(cdb));
+        cdb[0] = 0xa1;                        /* OPERATION CODE: 12 byte pass through */
+        cdb[1] = 4 << 1;                /* PROTOCOL: PIO Data-in */
+        cdb[2] = 0x2e;                        /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
+        cdb[3] = 0;                        /* FEATURES */
+        cdb[4] = 1;                        /* SECTORS */
+        cdb[5] = 0;                        /* LBA LOW */
+        cdb[6] = 0;                        /* LBA MID */
+        cdb[7] = 0;                        /* LBA HIGH */
+        cdb[8] = 0 & 0x4F;                /* SELECT */
+        cdb[9] = 0xEC;                        /* Command: ATA IDENTIFY DEVICE */;
+        memset(sense, 0, sizeof(sense));
+
+        memset(&io_v4, 0, sizeof(struct sg_io_v4));
+        io_v4.guard = 'Q';
+        io_v4.protocol = BSG_PROTOCOL_SCSI;
+        io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+        io_v4.request_len = sizeof (cdb);
+        io_v4.request = (uintptr_t) cdb;
+        io_v4.max_response_len = sizeof (sense);
+        io_v4.response = (uintptr_t) sense;
+        io_v4.din_xfer_len = buf_len;
+        io_v4.din_xferp = (uintptr_t) buf;
+        io_v4.timeout = COMMAND_TIMEOUT_MSEC;
+
+        ret = ioctl(fd, SG_IO, &io_v4);
+        if (ret != 0) {
+                /* could be that the driver doesn't do version 4, try version 3 */
+                if (errno == EINVAL) {
+                        struct sg_io_hdr io_hdr;
+
+                        memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+                        io_hdr.interface_id = 'S';
+                        io_hdr.cmdp = (unsigned char*) cdb;
+                        io_hdr.cmd_len = sizeof (cdb);
+                        io_hdr.dxferp = buf;
+                        io_hdr.dxfer_len = buf_len;
+                        io_hdr.sbp = sense;
+                        io_hdr.mx_sb_len = sizeof (sense);
+                        io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+                        io_hdr.timeout = COMMAND_TIMEOUT_MSEC;
+
+                        ret = ioctl(fd, SG_IO, &io_hdr);
+                        if (ret != 0)
+                                goto out;
+                } else {
+                        goto out;
+                }
+        }
+
+        if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) {
+                errno = EIO;
+                ret = -1;
+                goto out;
+        }
+
+ out:
+        return ret;
+}
+
+static int disk_identify_packet_device_command(int          fd,
+                                               void         *buf,
+                                               size_t          buf_len)
+{
+        struct sg_io_v4 io_v4;
+        uint8_t cdb[16];
+        uint8_t sense[32];
+        uint8_t *desc = sense+8;
+        int ret;
+
+        /*
+         * ATA Pass-Through 16 byte command, as described in
+         *
+         *  T10 04-262r8 ATA Command Pass-Through
+         *
+         * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf
+         */
+        memset(cdb, 0, sizeof(cdb));
+        cdb[0] = 0x85;                        /* OPERATION CODE: 16 byte pass through */
+        cdb[1] = 4 << 1;                /* PROTOCOL: PIO Data-in */
+        cdb[2] = 0x2e;                        /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
+        cdb[3] = 0;                        /* FEATURES */
+        cdb[4] = 0;                        /* FEATURES */
+        cdb[5] = 0;                        /* SECTORS */
+        cdb[6] = 1;                        /* SECTORS */
+        cdb[7] = 0;                        /* LBA LOW */
+        cdb[8] = 0;                        /* LBA LOW */
+        cdb[9] = 0;                        /* LBA MID */
+        cdb[10] = 0;                        /* LBA MID */
+        cdb[11] = 0;                        /* LBA HIGH */
+        cdb[12] = 0;                        /* LBA HIGH */
+        cdb[13] = 0;                        /* DEVICE */
+        cdb[14] = 0xA1;                        /* Command: ATA IDENTIFY PACKET DEVICE */;
+        cdb[15] = 0;                        /* CONTROL */
+        memset(sense, 0, sizeof(sense));
+
+        memset(&io_v4, 0, sizeof(struct sg_io_v4));
+        io_v4.guard = 'Q';
+        io_v4.protocol = BSG_PROTOCOL_SCSI;
+        io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+        io_v4.request_len = sizeof (cdb);
+        io_v4.request = (uintptr_t) cdb;
+        io_v4.max_response_len = sizeof (sense);
+        io_v4.response = (uintptr_t) sense;
+        io_v4.din_xfer_len = buf_len;
+        io_v4.din_xferp = (uintptr_t) buf;
+        io_v4.timeout = COMMAND_TIMEOUT_MSEC;
+
+        ret = ioctl(fd, SG_IO, &io_v4);
+        if (ret != 0) {
+                /* could be that the driver doesn't do version 4, try version 3 */
+                if (errno == EINVAL) {
+                        struct sg_io_hdr io_hdr;
+
+                        memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+                        io_hdr.interface_id = 'S';
+                        io_hdr.cmdp = (unsigned char*) cdb;
+                        io_hdr.cmd_len = sizeof (cdb);
+                        io_hdr.dxferp = buf;
+                        io_hdr.dxfer_len = buf_len;
+                        io_hdr.sbp = sense;
+                        io_hdr.mx_sb_len = sizeof (sense);
+                        io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+                        io_hdr.timeout = COMMAND_TIMEOUT_MSEC;
+
+                        ret = ioctl(fd, SG_IO, &io_hdr);
+                        if (ret != 0)
+                                goto out;
+                } else {
+                        goto out;
+                }
+        }
+
+        if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) {
+                errno = EIO;
+                ret = -1;
+                goto out;
+        }
+
+ out:
+        return ret;
+}
+
+/**
+ * disk_identify_get_string:
+ * @identify: A block of IDENTIFY data
+ * @offset_words: Offset of the string to get, in words.
+ * @dest: Destination buffer for the string.
+ * @dest_len: Length of destination buffer, in bytes.
+ *
+ * Copies the ATA string from @identify located at @offset_words into @dest.
+ */
+static void disk_identify_get_string(uint8_t identify[512],
+                                     unsigned int offset_words,
+                                     char *dest,
+                                     size_t dest_len)
+{
+        unsigned int c1;
+        unsigned int c2;
+
+        assert(identify != NULL);
+        assert(dest != NULL);
+        assert((dest_len & 1) == 0);
+
+        while (dest_len > 0) {
+                c1 = identify[offset_words * 2 + 1];
+                c2 = identify[offset_words * 2];
+                *dest = c1;
+                dest++;
+                *dest = c2;
+                dest++;
+                offset_words++;
+                dest_len -= 2;
+        }
+}
+
+static void disk_identify_fixup_string(uint8_t identify[512],
+                                       unsigned int offset_words,
+                                       size_t len)
+{
+        disk_identify_get_string(identify, offset_words,
+                                 (char *) identify + offset_words * 2, len);
+}
+
+static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned int offset_words)
+{
+        uint16_t *p;
+
+        p = (uint16_t *) identify;
+        p[offset_words] = le16toh (p[offset_words]);
+}
+
+/**
+ * disk_identify:
+ * @udev: The libudev context.
+ * @fd: File descriptor for the block device.
+ * @out_identify: Return location for IDENTIFY data.
+ * @out_is_packet_device: Return location for whether returned data is from a IDENTIFY PACKET DEVICE.
+ *
+ * Sends the IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command to the
+ * device represented by @fd. If successful, then the result will be
+ * copied into @out_identify and @out_is_packet_device.
+ *
+ * This routine is based on code from libatasmart, Copyright 2008
+ * Lennart Poettering, LGPL v2.1.
+ *
+ * Returns: 0 if the data was successfully obtained, otherwise
+ * non-zero with errno set.
+ */
+static int disk_identify(struct udev *udev,
+                         int               fd,
+                         uint8_t      out_identify[512],
+                         int              *out_is_packet_device)
+{
+        int ret;
+        uint8_t inquiry_buf[36];
+        int peripheral_device_type;
+        int all_nul_bytes;
+        int n;
+        int is_packet_device;
+
+        assert(out_identify != NULL);
+
+        /* init results */
+        ret = -1;
+        memset(out_identify, '\0', 512);
+        is_packet_device = 0;
+
+        /* If we were to use ATA PASS_THROUGH (12) on an ATAPI device
+         * we could accidentally blank media. This is because MMC's BLANK
+         * command has the same op-code (0x61).
+         *
+         * To prevent this from happening we bail out if the device
+         * isn't a Direct Access Block Device, e.g. SCSI type 0x00
+         * (CD/DVD devices are type 0x05). So we send a SCSI INQUIRY
+         * command first... libata is handling this via its SCSI
+         * emulation layer.
+         *
+         * This also ensures that we're actually dealing with a device
+         * that understands SCSI commands.
+         *
+         * (Yes, it is a bit perverse that we're tunneling the ATA
+         * command through SCSI and relying on the ATA driver
+         * emulating SCSI well-enough...)
+         *
+         * (See commit 160b069c25690bfb0c785994c7c3710289179107 for
+         * the original bug-fix and see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=556635
+         * for the original bug-report.)
+         */
+        ret = disk_scsi_inquiry_command (fd, inquiry_buf, sizeof (inquiry_buf));
+        if (ret != 0)
+                goto out;
+
+        /* SPC-4, section 6.4.2: Standard INQUIRY data */
+        peripheral_device_type = inquiry_buf[0] & 0x1f;
+        if (peripheral_device_type == 0x05)
+          {
+            is_packet_device = 1;
+            ret = disk_identify_packet_device_command(fd, out_identify, 512);
+            goto check_nul_bytes;
+          }
+        if (peripheral_device_type != 0x00) {
+                ret = -1;
+                errno = EIO;
+                goto out;
+        }
+
+        /* OK, now issue the IDENTIFY DEVICE command */
+        ret = disk_identify_command(fd, out_identify, 512);
+        if (ret != 0)
+                goto out;
+
+ check_nul_bytes:
+         /* Check if IDENTIFY data is all NUL bytes - if so, bail */
+        all_nul_bytes = 1;
+        for (n = 0; n < 512; n++) {
+                if (out_identify[n] != '\0') {
+                        all_nul_bytes = 0;
+                        break;
+                }
+        }
+
+        if (all_nul_bytes) {
+                ret = -1;
+                errno = EIO;
+                goto out;
+        }
+
+out:
+        if (out_is_packet_device != NULL)
+          *out_is_packet_device = is_packet_device;
+        return ret;
+}
+
+static void log_fn(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        vsyslog(priority, format, args);
+}
+
+int main(int argc, char *argv[])
+{
+        struct udev *udev;
+        struct hd_driveid id;
+        uint8_t identify[512];
+        uint16_t *identify_words;
+        char model[41];
+        char model_enc[256];
+        char serial[21];
+        char revision[9];
+        const char *node = NULL;
+        int export = 0;
+        int fd;
+        uint16_t word;
+        int rc = 0;
+        int is_packet_device = 0;
+        static const struct option options[] = {
+                { "export", no_argument, NULL, 'x' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+
+        udev = udev_new();
+        if (udev == NULL)
+                goto exit;
+
+        udev_log_init("ata_id");
+        udev_set_log_fn(udev, log_fn);
+
+        while (1) {
+                int option;
+
+                option = getopt_long(argc, argv, "xh", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'x':
+                        export = 1;
+                        break;
+                case 'h':
+                        printf("Usage: ata_id [--export] [--help] <device>\n"
+                               "  --export    print values as environment keys\n"
+                               "  --help      print this help text\n\n");
+                        goto exit;
+                }
+        }
+
+        node = argv[optind];
+        if (node == NULL) {
+                err(udev, "no node specified\n");
+                rc = 1;
+                goto exit;
+        }
+
+        fd = open(node, O_RDONLY|O_NONBLOCK);
+        if (fd < 0) {
+                err(udev, "unable to open '%s'\n", node);
+                rc = 1;
+                goto exit;
+        }
+
+        if (disk_identify(udev, fd, identify, &is_packet_device) == 0) {
+                /*
+                 * fix up only the fields from the IDENTIFY data that we are going to
+                 * use and copy it into the hd_driveid struct for convenience
+                 */
+                disk_identify_fixup_string (identify,  10, 20); /* serial */
+                disk_identify_fixup_string (identify,  23,  6); /* fwrev */
+                disk_identify_fixup_string (identify,  27, 40); /* model */
+                disk_identify_fixup_uint16 (identify,  0);      /* configuration */
+                disk_identify_fixup_uint16 (identify,  75);     /* queue depth */
+                disk_identify_fixup_uint16 (identify,  75);     /* SATA capabilities */
+                disk_identify_fixup_uint16 (identify,  82);     /* command set supported */
+                disk_identify_fixup_uint16 (identify,  83);     /* command set supported */
+                disk_identify_fixup_uint16 (identify,  84);     /* command set supported */
+                disk_identify_fixup_uint16 (identify,  85);     /* command set supported */
+                disk_identify_fixup_uint16 (identify,  86);     /* command set supported */
+                disk_identify_fixup_uint16 (identify,  87);     /* command set supported */
+                disk_identify_fixup_uint16 (identify,  89);     /* time required for SECURITY ERASE UNIT */
+                disk_identify_fixup_uint16 (identify,  90);     /* time required for enhanced SECURITY ERASE UNIT */
+                disk_identify_fixup_uint16 (identify,  91);     /* current APM values */
+                disk_identify_fixup_uint16 (identify,  94);     /* current AAM value */
+                disk_identify_fixup_uint16 (identify, 128);     /* device lock function */
+                disk_identify_fixup_uint16 (identify, 217);     /* nominal media rotation rate */
+                memcpy(&id, identify, sizeof id);
+        } else {
+                /* If this fails, then try HDIO_GET_IDENTITY */
+                if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) {
+                        info(udev, "HDIO_GET_IDENTITY failed for '%s': %m\n", node);
+                        rc = 2;
+                        goto close;
+                }
+        }
+        identify_words = (uint16_t *) identify;
+
+        memcpy (model, id.model, 40);
+        model[40] = '\0';
+        udev_util_encode_string(model, model_enc, sizeof(model_enc));
+        util_replace_whitespace((char *) id.model, model, 40);
+        util_replace_chars(model, NULL);
+        util_replace_whitespace((char *) id.serial_no, serial, 20);
+        util_replace_chars(serial, NULL);
+        util_replace_whitespace((char *) id.fw_rev, revision, 8);
+        util_replace_chars(revision, NULL);
+
+        if (export) {
+                /* Set this to convey the disk speaks the ATA protocol */
+                printf("ID_ATA=1\n");
+
+                if ((id.config >> 8) & 0x80) {
+                        /* This is an ATAPI device */
+                        switch ((id.config >> 8) & 0x1f) {
+                        case 0:
+                                printf("ID_TYPE=cd\n");
+                                break;
+                        case 1:
+                                printf("ID_TYPE=tape\n");
+                                break;
+                        case 5:
+                                printf("ID_TYPE=cd\n");
+                                break;
+                        case 7:
+                                printf("ID_TYPE=optical\n");
+                                break;
+                        default:
+                                printf("ID_TYPE=generic\n");
+                                break;
+                        }
+                } else {
+                        printf("ID_TYPE=disk\n");
+                }
+                printf("ID_BUS=ata\n");
+                printf("ID_MODEL=%s\n", model);
+                printf("ID_MODEL_ENC=%s\n", model_enc);
+                printf("ID_REVISION=%s\n", revision);
+                if (serial[0] != '\0') {
+                        printf("ID_SERIAL=%s_%s\n", model, serial);
+                        printf("ID_SERIAL_SHORT=%s\n", serial);
+                } else {
+                        printf("ID_SERIAL=%s\n", model);
+                }
+
+                if (id.command_set_1 & (1<<5)) {
+                        printf ("ID_ATA_WRITE_CACHE=1\n");
+                        printf ("ID_ATA_WRITE_CACHE_ENABLED=%d\n", (id.cfs_enable_1 & (1<<5)) ? 1 : 0);
+                }
+                if (id.command_set_1 & (1<<10)) {
+                        printf("ID_ATA_FEATURE_SET_HPA=1\n");
+                        printf("ID_ATA_FEATURE_SET_HPA_ENABLED=%d\n", (id.cfs_enable_1 & (1<<10)) ? 1 : 0);
+
+                        /*
+                         * TODO: use the READ NATIVE MAX ADDRESS command to get the native max address
+                         * so it is easy to check whether the protected area is in use.
+                         */
+                }
+                if (id.command_set_1 & (1<<3)) {
+                        printf("ID_ATA_FEATURE_SET_PM=1\n");
+                        printf("ID_ATA_FEATURE_SET_PM_ENABLED=%d\n", (id.cfs_enable_1 & (1<<3)) ? 1 : 0);
+                }
+                if (id.command_set_1 & (1<<1)) {
+                        printf("ID_ATA_FEATURE_SET_SECURITY=1\n");
+                        printf("ID_ATA_FEATURE_SET_SECURITY_ENABLED=%d\n", (id.cfs_enable_1 & (1<<1)) ? 1 : 0);
+                        printf("ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=%d\n", id.trseuc * 2);
+                        if ((id.cfs_enable_1 & (1<<1))) /* enabled */ {
+                                if (id.dlf & (1<<8))
+                                        printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=maximum\n");
+                                else
+                                        printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=high\n");
+                        }
+                        if (id.dlf & (1<<5))
+                                printf("ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=%d\n", id.trsEuc * 2);
+                        if (id.dlf & (1<<4))
+                                printf("ID_ATA_FEATURE_SET_SECURITY_EXPIRE=1\n");
+                        if (id.dlf & (1<<3))
+                                printf("ID_ATA_FEATURE_SET_SECURITY_FROZEN=1\n");
+                        if (id.dlf & (1<<2))
+                                printf("ID_ATA_FEATURE_SET_SECURITY_LOCKED=1\n");
+                }
+                if (id.command_set_1 & (1<<0)) {
+                        printf("ID_ATA_FEATURE_SET_SMART=1\n");
+                        printf("ID_ATA_FEATURE_SET_SMART_ENABLED=%d\n", (id.cfs_enable_1 & (1<<0)) ? 1 : 0);
+                }
+                if (id.command_set_2 & (1<<9)) {
+                        printf("ID_ATA_FEATURE_SET_AAM=1\n");
+                        printf("ID_ATA_FEATURE_SET_AAM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<9)) ? 1 : 0);
+                        printf("ID_ATA_FEATURE_SET_AAM_VENDOR_RECOMMENDED_VALUE=%d\n", id.acoustic >> 8);
+                        printf("ID_ATA_FEATURE_SET_AAM_CURRENT_VALUE=%d\n", id.acoustic & 0xff);
+                }
+                if (id.command_set_2 & (1<<5)) {
+                        printf("ID_ATA_FEATURE_SET_PUIS=1\n");
+                        printf("ID_ATA_FEATURE_SET_PUIS_ENABLED=%d\n", (id.cfs_enable_2 & (1<<5)) ? 1 : 0);
+                }
+                if (id.command_set_2 & (1<<3)) {
+                        printf("ID_ATA_FEATURE_SET_APM=1\n");
+                        printf("ID_ATA_FEATURE_SET_APM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<3)) ? 1 : 0);
+                        if ((id.cfs_enable_2 & (1<<3)))
+                                printf("ID_ATA_FEATURE_SET_APM_CURRENT_VALUE=%d\n", id.CurAPMvalues & 0xff);
+                }
+                if (id.command_set_2 & (1<<0))
+                        printf("ID_ATA_DOWNLOAD_MICROCODE=1\n");
+
+                /*
+                 * Word 76 indicates the capabilities of a SATA device. A PATA device shall set
+                 * word 76 to 0000h or FFFFh. If word 76 is set to 0000h or FFFFh, then
+                 * the device does not claim compliance with the Serial ATA specification and words
+                 * 76 through 79 are not valid and shall be ignored.
+                 */
+                word = *((uint16_t *) identify + 76);
+                if (word != 0x0000 && word != 0xffff) {
+                        printf("ID_ATA_SATA=1\n");
+                        /*
+                         * If bit 2 of word 76 is set to one, then the device supports the Gen2
+                         * signaling rate of 3.0 Gb/s (see SATA 2.6).
+                         *
+                         * If bit 1 of word 76 is set to one, then the device supports the Gen1
+                         * signaling rate of 1.5 Gb/s (see SATA 2.6).
+                         */
+                        if (word & (1<<2))
+                                printf("ID_ATA_SATA_SIGNAL_RATE_GEN2=1\n");
+                        if (word & (1<<1))
+                                printf("ID_ATA_SATA_SIGNAL_RATE_GEN1=1\n");
+                }
+
+                /* Word 217 indicates the nominal media rotation rate of the device */
+                word = *((uint16_t *) identify + 217);
+                if (word != 0x0000) {
+                        if (word == 0x0001) {
+                                printf ("ID_ATA_ROTATION_RATE_RPM=0\n"); /* non-rotating e.g. SSD */
+                        } else if (word >= 0x0401 && word <= 0xfffe) {
+                                printf ("ID_ATA_ROTATION_RATE_RPM=%d\n", word);
+                        }
+                }
+
+                /*
+                 * Words 108-111 contain a mandatory World Wide Name (WWN) in the NAA IEEE Registered identifier
+                 * format. Word 108 bits (15:12) shall contain 5h, indicating that the naming authority is IEEE.
+                 * All other values are reserved.
+                 */
+                word = *((uint16_t *) identify + 108);
+                if ((word & 0xf000) == 0x5000) {
+                        uint64_t wwwn;
+
+                        wwwn   = *((uint16_t *) identify + 108);
+                        wwwn <<= 16;
+                        wwwn  |= *((uint16_t *) identify + 109);
+                        wwwn <<= 16;
+                        wwwn  |= *((uint16_t *) identify + 110);
+                        wwwn <<= 16;
+                        wwwn  |= *((uint16_t *) identify + 111);
+                        printf("ID_WWN=0x%llx\n", (unsigned long long int) wwwn);
+                        /* ATA devices have no vendor extension */
+                        printf("ID_WWN_WITH_EXTENSION=0x%llx\n", (unsigned long long int) wwwn);
+                }
+
+                /* from Linux's include/linux/ata.h */
+                if (identify_words[0] == 0x848a || identify_words[0] == 0x844a) {
+                        printf("ID_ATA_CFA=1\n");
+                } else {
+                        if ((identify_words[83] & 0xc004) == 0x4004) {
+                                printf("ID_ATA_CFA=1\n");
+                        }
+                }
+        } else {
+                if (serial[0] != '\0')
+                        printf("%s_%s\n", model, serial);
+                else
+                        printf("%s\n", model);
+        }
+close:
+        close(fd);
+exit:
+        udev_unref(udev);
+        udev_log_close();
+        return rc;
+}
diff --git a/lib/udev/rules.d/60-cdrom_id.rules b/src/cdrom_id/60-cdrom_id.rules
similarity index 68%
rename from lib/udev/rules.d/60-cdrom_id.rules
rename to src/cdrom_id/60-cdrom_id.rules
index 896af34..6eaf76a 100644
--- a/lib/udev/rules.d/60-cdrom_id.rules
+++ b/src/cdrom_id/60-cdrom_id.rules
@@ -9,10 +9,12 @@
 KERNEL=="sr[0-9]*", ENV{ID_CDROM}="1"
 
 # media eject button pressed
-ENV{DISK_EJECT_REQUEST}=="?*", RUN+="cdrom_id --eject-media $tempnode", GOTO="cdrom_end"
+ENV{DISK_EJECT_REQUEST}=="?*", RUN+="cdrom_id --eject-media $devnode", GOTO="cdrom_end"
 
 # import device and media properties and lock tray to
 # enable the receiving of media eject button events
-IMPORT{program}="cdrom_id --lock-media $tempnode"
+IMPORT{program}="cdrom_id --lock-media $devnode"
+
+KERNEL=="sr0", SYMLINK+="cdrom", OPTIONS+="link_priority=-100"
 
 LABEL="cdrom_end"
diff --git a/src/cdrom_id/cdrom_id.c b/src/cdrom_id/cdrom_id.c
new file mode 100644
index 0000000..f90d52e
--- /dev/null
+++ b/src/cdrom_id/cdrom_id.c
@@ -0,0 +1,1099 @@
+/*
+ * cdrom_id - optical drive and media information prober
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <getopt.h>
+#include <time.h>
+#include <scsi/sg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <linux/cdrom.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static bool debug;
+
+static void log_fn(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        if (debug) {
+                fprintf(stderr, "%s: ", fn);
+                vfprintf(stderr, format, args);
+        } else {
+                vsyslog(priority, format, args);
+        }
+}
+
+/* device info */
+static unsigned int cd_cd_rom;
+static unsigned int cd_cd_r;
+static unsigned int cd_cd_rw;
+static unsigned int cd_dvd_rom;
+static unsigned int cd_dvd_r;
+static unsigned int cd_dvd_rw;
+static unsigned int cd_dvd_ram;
+static unsigned int cd_dvd_plus_r;
+static unsigned int cd_dvd_plus_rw;
+static unsigned int cd_dvd_plus_r_dl;
+static unsigned int cd_dvd_plus_rw_dl;
+static unsigned int cd_bd;
+static unsigned int cd_bd_r;
+static unsigned int cd_bd_re;
+static unsigned int cd_hddvd;
+static unsigned int cd_hddvd_r;
+static unsigned int cd_hddvd_rw;
+static unsigned int cd_mo;
+static unsigned int cd_mrw;
+static unsigned int cd_mrw_w;
+
+/* media info */
+static unsigned int cd_media;
+static unsigned int cd_media_cd_rom;
+static unsigned int cd_media_cd_r;
+static unsigned int cd_media_cd_rw;
+static unsigned int cd_media_dvd_rom;
+static unsigned int cd_media_dvd_r;
+static unsigned int cd_media_dvd_rw;
+static unsigned int cd_media_dvd_rw_ro; /* restricted overwrite mode */
+static unsigned int cd_media_dvd_rw_seq; /* sequential mode */
+static unsigned int cd_media_dvd_ram;
+static unsigned int cd_media_dvd_plus_r;
+static unsigned int cd_media_dvd_plus_rw;
+static unsigned int cd_media_dvd_plus_r_dl;
+static unsigned int cd_media_dvd_plus_rw_dl;
+static unsigned int cd_media_bd;
+static unsigned int cd_media_bd_r;
+static unsigned int cd_media_bd_re;
+static unsigned int cd_media_hddvd;
+static unsigned int cd_media_hddvd_r;
+static unsigned int cd_media_hddvd_rw;
+static unsigned int cd_media_mo;
+static unsigned int cd_media_mrw;
+static unsigned int cd_media_mrw_w;
+
+static const char *cd_media_state = NULL;
+static unsigned int cd_media_session_next;
+static unsigned int cd_media_session_count;
+static unsigned int cd_media_track_count;
+static unsigned int cd_media_track_count_data;
+static unsigned int cd_media_track_count_audio;
+static unsigned long long int cd_media_session_last_offset;
+
+#define ERRCODE(s)        ((((s)[2] & 0x0F) << 16) | ((s)[12] << 8) | ((s)[13]))
+#define SK(errcode)        (((errcode) >> 16) & 0xF)
+#define ASC(errcode)        (((errcode) >> 8) & 0xFF)
+#define ASCQ(errcode)        ((errcode) & 0xFF)
+
+static bool is_mounted(const char *device)
+{
+        struct stat statbuf;
+        FILE *fp;
+        int maj, min;
+        bool mounted = false;
+
+        if (stat(device, &statbuf) < 0)
+                return -ENODEV;
+
+        fp = fopen("/proc/self/mountinfo", "r");
+        if (fp == NULL)
+                return -ENOSYS;
+        while (fscanf(fp, "%*s %*s %i:%i %*[^\n]", &maj, &min) == 2) {
+                if (makedev(maj, min) == statbuf.st_rdev) {
+                        mounted = true;
+                        break;
+                }
+        }
+        fclose(fp);
+        return mounted;
+}
+
+static void info_scsi_cmd_err(struct udev *udev, char *cmd, int err)
+{
+        if (err == -1) {
+                info(udev, "%s failed\n", cmd);
+                return;
+        }
+        info(udev, "%s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh\n", cmd, SK(err), ASC(err), ASCQ(err));
+}
+
+struct scsi_cmd {
+        struct cdrom_generic_command cgc;
+        union {
+                struct request_sense s;
+                unsigned char u[18];
+        } _sense;
+        struct sg_io_hdr sg_io;
+};
+
+static void scsi_cmd_init(struct udev *udev, struct scsi_cmd *cmd)
+{
+        memset(cmd, 0x00, sizeof(struct scsi_cmd));
+        cmd->cgc.quiet = 1;
+        cmd->cgc.sense = &cmd->_sense.s;
+        cmd->sg_io.interface_id = 'S';
+        cmd->sg_io.mx_sb_len = sizeof(cmd->_sense);
+        cmd->sg_io.cmdp = cmd->cgc.cmd;
+        cmd->sg_io.sbp = cmd->_sense.u;
+        cmd->sg_io.flags = SG_FLAG_LUN_INHIBIT | SG_FLAG_DIRECT_IO;
+}
+
+static void scsi_cmd_set(struct udev *udev, struct scsi_cmd *cmd, size_t i, unsigned char arg)
+{
+        cmd->sg_io.cmd_len = i + 1;
+        cmd->cgc.cmd[i] = arg;
+}
+
+#define CHECK_CONDITION 0x01
+
+static int scsi_cmd_run(struct udev *udev, struct scsi_cmd *cmd, int fd, unsigned char *buf, size_t bufsize)
+{
+        int ret = 0;
+
+        if (bufsize > 0) {
+                cmd->sg_io.dxferp = buf;
+                cmd->sg_io.dxfer_len = bufsize;
+                cmd->sg_io.dxfer_direction = SG_DXFER_FROM_DEV;
+        } else {
+                cmd->sg_io.dxfer_direction = SG_DXFER_NONE;
+        }
+        if (ioctl(fd, SG_IO, &cmd->sg_io))
+                return -1;
+
+        if ((cmd->sg_io.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
+                errno = EIO;
+                ret = -1;
+                if (cmd->sg_io.masked_status & CHECK_CONDITION) {
+                        ret = ERRCODE(cmd->_sense.u);
+                        if (ret == 0)
+                                ret = -1;
+                }
+        }
+        return ret;
+}
+
+static int media_lock(struct udev *udev, int fd, bool lock)
+{
+        int err;
+
+        /* disable the kernel's lock logic */
+        err = ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_LOCK);
+        if (err < 0)
+                info(udev, "CDROM_CLEAR_OPTIONS, CDO_LOCK failed\n");
+
+        err = ioctl(fd, CDROM_LOCKDOOR, lock ? 1 : 0);
+        if (err < 0)
+                info(udev, "CDROM_LOCKDOOR failed\n");
+
+        return err;
+}
+
+static int media_eject(struct udev *udev, int fd)
+{
+        struct scsi_cmd sc;
+        int err;
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x1b);
+        scsi_cmd_set(udev, &sc, 4, 0x02);
+        scsi_cmd_set(udev, &sc, 5, 0);
+        err = scsi_cmd_run(udev, &sc, fd, NULL, 0);
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "START_STOP_UNIT", err);
+                return -1;
+        }
+        return 0;
+}
+
+static int cd_capability_compat(struct udev *udev, int fd)
+{
+        int capability;
+
+        capability = ioctl(fd, CDROM_GET_CAPABILITY, NULL);
+        if (capability < 0) {
+                info(udev, "CDROM_GET_CAPABILITY failed\n");
+                return -1;
+        }
+
+        if (capability & CDC_CD_R)
+                cd_cd_r = 1;
+        if (capability & CDC_CD_RW)
+                cd_cd_rw = 1;
+        if (capability & CDC_DVD)
+                cd_dvd_rom = 1;
+        if (capability & CDC_DVD_R)
+                cd_dvd_r = 1;
+        if (capability & CDC_DVD_RAM)
+                cd_dvd_ram = 1;
+        if (capability & CDC_MRW)
+                cd_mrw = 1;
+        if (capability & CDC_MRW_W)
+                cd_mrw_w = 1;
+        return 0;
+}
+
+static int cd_media_compat(struct udev *udev, int fd)
+{
+        if (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT) != CDS_DISC_OK) {
+                info(udev, "CDROM_DRIVE_STATUS != CDS_DISC_OK\n");
+                return -1;
+        }
+        cd_media = 1;
+        return 0;
+}
+
+static int cd_inquiry(struct udev *udev, int fd)
+{
+        struct scsi_cmd sc;
+        unsigned char inq[128];
+        int err;
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x12);
+        scsi_cmd_set(udev, &sc, 4, 36);
+        scsi_cmd_set(udev, &sc, 5, 0);
+        err = scsi_cmd_run(udev, &sc, fd, inq, 36);
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "INQUIRY", err);
+                return -1;
+        }
+
+        if ((inq[0] & 0x1F) != 5) {
+                info(udev, "not an MMC unit\n");
+                return -1;
+        }
+
+        info(udev, "INQUIRY: [%.8s][%.16s][%.4s]\n", inq + 8, inq + 16, inq + 32);
+        return 0;
+}
+
+static void feature_profile_media(struct udev *udev, int cur_profile)
+{
+        switch (cur_profile) {
+        case 0x03:
+        case 0x04:
+        case 0x05:
+                info(udev, "profile 0x%02x \n", cur_profile);
+                cd_media = 1;
+                cd_media_mo = 1;
+                break;
+        case 0x08:
+                info(udev, "profile 0x%02x media_cd_rom\n", cur_profile);
+                cd_media = 1;
+                cd_media_cd_rom = 1;
+                break;
+        case 0x09:
+                info(udev, "profile 0x%02x media_cd_r\n", cur_profile);
+                cd_media = 1;
+                cd_media_cd_r = 1;
+                break;
+        case 0x0a:
+                info(udev, "profile 0x%02x media_cd_rw\n", cur_profile);
+                cd_media = 1;
+                cd_media_cd_rw = 1;
+                break;
+        case 0x10:
+                info(udev, "profile 0x%02x media_dvd_ro\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_rom = 1;
+                break;
+        case 0x11:
+                info(udev, "profile 0x%02x media_dvd_r\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_r = 1;
+                break;
+        case 0x12:
+                info(udev, "profile 0x%02x media_dvd_ram\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_ram = 1;
+                break;
+        case 0x13:
+                info(udev, "profile 0x%02x media_dvd_rw_ro\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_rw = 1;
+                cd_media_dvd_rw_ro = 1;
+                break;
+        case 0x14:
+                info(udev, "profile 0x%02x media_dvd_rw_seq\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_rw = 1;
+                cd_media_dvd_rw_seq = 1;
+                break;
+        case 0x1B:
+                info(udev, "profile 0x%02x media_dvd_plus_r\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_plus_r = 1;
+                break;
+        case 0x1A:
+                info(udev, "profile 0x%02x media_dvd_plus_rw\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_plus_rw = 1;
+                break;
+        case 0x2A:
+                info(udev, "profile 0x%02x media_dvd_plus_rw_dl\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_plus_rw_dl = 1;
+                break;
+        case 0x2B:
+                info(udev, "profile 0x%02x media_dvd_plus_r_dl\n", cur_profile);
+                cd_media = 1;
+                cd_media_dvd_plus_r_dl = 1;
+                break;
+        case 0x40:
+                info(udev, "profile 0x%02x media_bd\n", cur_profile);
+                cd_media = 1;
+                cd_media_bd = 1;
+                break;
+        case 0x41:
+        case 0x42:
+                info(udev, "profile 0x%02x media_bd_r\n", cur_profile);
+                cd_media = 1;
+                cd_media_bd_r = 1;
+                break;
+        case 0x43:
+                info(udev, "profile 0x%02x media_bd_re\n", cur_profile);
+                cd_media = 1;
+                cd_media_bd_re = 1;
+                break;
+        case 0x50:
+                info(udev, "profile 0x%02x media_hddvd\n", cur_profile);
+                cd_media = 1;
+                cd_media_hddvd = 1;
+                break;
+        case 0x51:
+                info(udev, "profile 0x%02x media_hddvd_r\n", cur_profile);
+                cd_media = 1;
+                cd_media_hddvd_r = 1;
+                break;
+        case 0x52:
+                info(udev, "profile 0x%02x media_hddvd_rw\n", cur_profile);
+                cd_media = 1;
+                cd_media_hddvd_rw = 1;
+                break;
+        default:
+                info(udev, "profile 0x%02x <ignored>\n", cur_profile);
+                break;
+        }
+}
+
+static int feature_profiles(struct udev *udev, const unsigned char *profiles, size_t size)
+{
+        unsigned int i;
+
+        for (i = 0; i+4 <= size; i += 4) {
+                int profile;
+
+                profile = profiles[i] << 8 | profiles[i+1];
+                switch (profile) {
+                case 0x03:
+                case 0x04:
+                case 0x05:
+                        info(udev, "profile 0x%02x mo\n", profile);
+                        cd_mo = 1;
+                        break;
+                case 0x08:
+                        info(udev, "profile 0x%02x cd_rom\n", profile);
+                        cd_cd_rom = 1;
+                        break;
+                case 0x09:
+                        info(udev, "profile 0x%02x cd_r\n", profile);
+                        cd_cd_r = 1;
+                        break;
+                case 0x0A:
+                        info(udev, "profile 0x%02x cd_rw\n", profile);
+                        cd_cd_rw = 1;
+                        break;
+                case 0x10:
+                        info(udev, "profile 0x%02x dvd_rom\n", profile);
+                        cd_dvd_rom = 1;
+                        break;
+                case 0x12:
+                        info(udev, "profile 0x%02x dvd_ram\n", profile);
+                        cd_dvd_ram = 1;
+                        break;
+                case 0x13:
+                case 0x14:
+                        info(udev, "profile 0x%02x dvd_rw\n", profile);
+                        cd_dvd_rw = 1;
+                        break;
+                case 0x1B:
+                        info(udev, "profile 0x%02x dvd_plus_r\n", profile);
+                        cd_dvd_plus_r = 1;
+                        break;
+                case 0x1A:
+                        info(udev, "profile 0x%02x dvd_plus_rw\n", profile);
+                        cd_dvd_plus_rw = 1;
+                        break;
+                case 0x2A:
+                        info(udev, "profile 0x%02x dvd_plus_rw_dl\n", profile);
+                        cd_dvd_plus_rw_dl = 1;
+                        break;
+                case 0x2B:
+                        info(udev, "profile 0x%02x dvd_plus_r_dl\n", profile);
+                        cd_dvd_plus_r_dl = 1;
+                        break;
+                case 0x40:
+                        cd_bd = 1;
+                        info(udev, "profile 0x%02x bd\n", profile);
+                        break;
+                case 0x41:
+                case 0x42:
+                        cd_bd_r = 1;
+                        info(udev, "profile 0x%02x bd_r\n", profile);
+                        break;
+                case 0x43:
+                        cd_bd_re = 1;
+                        info(udev, "profile 0x%02x bd_re\n", profile);
+                        break;
+                case 0x50:
+                        cd_hddvd = 1;
+                        info(udev, "profile 0x%02x hddvd\n", profile);
+                        break;
+                case 0x51:
+                        cd_hddvd_r = 1;
+                        info(udev, "profile 0x%02x hddvd_r\n", profile);
+                        break;
+                case 0x52:
+                        cd_hddvd_rw = 1;
+                        info(udev, "profile 0x%02x hddvd_rw\n", profile);
+                        break;
+                default:
+                        info(udev, "profile 0x%02x <ignored>\n", profile);
+                        break;
+                }
+        }
+        return 0;
+}
+
+/* returns 0 if media was detected */
+static int cd_profiles_old_mmc(struct udev *udev, int fd)
+{
+        struct scsi_cmd sc;
+        int err;
+
+        unsigned char header[32];
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x51);
+        scsi_cmd_set(udev, &sc, 8, sizeof(header));
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "READ DISC INFORMATION", err);
+                if (cd_media == 1) {
+                        info(udev, "no current profile, but disc is present; assuming CD-ROM\n");
+                        cd_media_cd_rom = 1;
+                        return 0;
+                } else {
+                        info(udev, "no current profile, assuming no media\n");
+                        return -1;
+                }
+        };
+
+        cd_media = 1;
+
+        if (header[2] & 16) {
+                cd_media_cd_rw = 1;
+                info(udev, "profile 0x0a media_cd_rw\n");
+        } else if ((header[2] & 3) < 2 && cd_cd_r) {
+                cd_media_cd_r = 1;
+                info(udev, "profile 0x09 media_cd_r\n");
+        } else {
+                cd_media_cd_rom = 1;
+                info(udev, "profile 0x08 media_cd_rom\n");
+        }
+        return 0;
+}
+
+/* returns 0 if media was detected */
+static int cd_profiles(struct udev *udev, int fd)
+{
+        struct scsi_cmd sc;
+        unsigned char features[65530];
+        unsigned int cur_profile = 0;
+        unsigned int len;
+        unsigned int i;
+        int err;
+        int ret;
+
+        ret = -1;
+
+        /* First query the current profile */
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x46);
+        scsi_cmd_set(udev, &sc, 8, 8);
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, features, 8);
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "GET CONFIGURATION", err);
+                /* handle pre-MMC2 drives which do not support GET CONFIGURATION */
+                if (SK(err) == 0x5 && ASC(err) == 0x20) {
+                        info(udev, "drive is pre-MMC2 and does not support 46h get configuration command\n");
+                        info(udev, "trying to work around the problem\n");
+                        ret = cd_profiles_old_mmc(udev, fd);
+                }
+                goto out;
+        }
+
+        cur_profile = features[6] << 8 | features[7];
+        if (cur_profile > 0) {
+                info(udev, "current profile 0x%02x\n", cur_profile);
+                feature_profile_media (udev, cur_profile);
+                ret = 0; /* we have media */
+        } else {
+                info(udev, "no current profile, assuming no media\n");
+        }
+
+        len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3];
+        info(udev, "GET CONFIGURATION: size of features buffer 0x%04x\n", len);
+
+        if (len > sizeof(features)) {
+                info(udev, "can not get features in a single query, truncating\n");
+                len = sizeof(features);
+        } else if (len <= 8) {
+                len = sizeof(features);
+        }
+
+        /* Now get the full feature buffer */
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x46);
+        scsi_cmd_set(udev, &sc, 7, ( len >> 8 ) & 0xff);
+        scsi_cmd_set(udev, &sc, 8, len & 0xff);
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, features, len);
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "GET CONFIGURATION", err);
+                return -1;
+        }
+
+        /* parse the length once more, in case the drive decided to have other features suddenly :) */
+        len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3];
+        info(udev, "GET CONFIGURATION: size of features buffer 0x%04x\n", len);
+
+        if (len > sizeof(features)) {
+                info(udev, "can not get features in a single query, truncating\n");
+                len = sizeof(features);
+        }
+
+        /* device features */
+        for (i = 8; i+4 < len; i += (4 + features[i+3])) {
+                unsigned int feature;
+
+                feature = features[i] << 8 | features[i+1];
+
+                switch (feature) {
+                case 0x00:
+                        info(udev, "GET CONFIGURATION: feature 'profiles', with %i entries\n", features[i+3] / 4);
+                        feature_profiles(udev, &features[i]+4, features[i+3]);
+                        break;
+                default:
+                        info(udev, "GET CONFIGURATION: feature 0x%04x <ignored>, with 0x%02x bytes\n", feature, features[i+3]);
+                        break;
+                }
+        }
+out:
+        return ret;
+}
+
+static int cd_media_info(struct udev *udev, int fd)
+{
+        struct scsi_cmd sc;
+        unsigned char header[32];
+        static const char *media_status[] = {
+                "blank",
+                "appendable",
+                "complete",
+                "other"
+        };
+        int err;
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x51);
+        scsi_cmd_set(udev, &sc, 8, sizeof(header) & 0xff);
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "READ DISC INFORMATION", err);
+                return -1;
+        };
+
+        cd_media = 1;
+        info(udev, "disk type %02x\n", header[8]);
+        info(udev, "hardware reported media status: %s\n", media_status[header[2] & 3]);
+
+        /* exclude plain CDROM, some fake cdroms return 0 for "blank" media here */
+        if (!cd_media_cd_rom)
+                cd_media_state = media_status[header[2] & 3];
+
+        /* fresh DVD-RW in restricted overwite mode reports itself as
+         * "appendable"; change it to "blank" to make it consistent with what
+         * gets reported after blanking, and what userspace expects  */
+        if (cd_media_dvd_rw_ro && (header[2] & 3) == 1)
+                cd_media_state = media_status[0];
+
+        /* DVD+RW discs (and DVD-RW in restricted mode) once formatted are
+         * always "complete", DVD-RAM are "other" or "complete" if the disc is
+         * write protected; we need to check the contents if it is blank */
+        if ((cd_media_dvd_rw_ro || cd_media_dvd_plus_rw || cd_media_dvd_plus_rw_dl || cd_media_dvd_ram) && (header[2] & 3) > 1) {
+                unsigned char buffer[32 * 2048];
+                unsigned char result, len;
+                int block, offset;
+
+                if (cd_media_dvd_ram) {
+                        /* a write protected dvd-ram may report "complete" status */
+
+                        unsigned char dvdstruct[8];
+                        unsigned char format[12];
+
+                        scsi_cmd_init(udev, &sc);
+                        scsi_cmd_set(udev, &sc, 0, 0xAD);
+                        scsi_cmd_set(udev, &sc, 7, 0xC0);
+                        scsi_cmd_set(udev, &sc, 9, sizeof(dvdstruct));
+                        scsi_cmd_set(udev, &sc, 11, 0);
+                        err = scsi_cmd_run(udev, &sc, fd, dvdstruct, sizeof(dvdstruct));
+                        if ((err != 0)) {
+                                info_scsi_cmd_err(udev, "READ DVD STRUCTURE", err);
+                                return -1;
+                        }
+                        if (dvdstruct[4] & 0x02) {
+                                cd_media_state = media_status[2];
+                                info(udev, "write-protected DVD-RAM media inserted\n");
+                                goto determined;
+                        }
+
+                        /* let's make sure we don't try to read unformatted media */
+                        scsi_cmd_init(udev, &sc);
+                        scsi_cmd_set(udev, &sc, 0, 0x23);
+                        scsi_cmd_set(udev, &sc, 8, sizeof(format));
+                        scsi_cmd_set(udev, &sc, 9, 0);
+                        err = scsi_cmd_run(udev, &sc, fd, format, sizeof(format));
+                        if ((err != 0)) {
+                                info_scsi_cmd_err(udev, "READ DVD FORMAT CAPACITIES", err);
+                                return -1;
+                        }
+
+                        len = format[3];
+                        if (len & 7 || len < 16) {
+                                info(udev, "invalid format capacities length\n");
+                                return -1;
+                        }
+
+                        switch(format[8] & 3) {
+                            case 1:
+                                info(udev, "unformatted DVD-RAM media inserted\n");
+                                /* This means that last format was interrupted
+                                 * or failed, blank dvd-ram discs are factory
+                                 * formatted. Take no action here as it takes
+                                 * quite a while to reformat a dvd-ram and it's
+                                 * not automatically started */
+                                goto determined;
+
+                            case 2:
+                                info(udev, "formatted DVD-RAM media inserted\n");
+                                break;
+
+                            case 3:
+                                cd_media = 0; //return no media
+                                info(udev, "format capacities returned no media\n");
+                                return -1;
+                        }
+                }
+
+                /* Take a closer look at formatted media (unformatted DVD+RW
+                 * has "blank" status", DVD-RAM was examined earlier) and check
+                 * for ISO and UDF PVDs or a fs superblock presence and do it
+                 * in one ioctl (we need just sectors 0 and 16) */
+                scsi_cmd_init(udev, &sc);
+                scsi_cmd_set(udev, &sc, 0, 0x28);
+                scsi_cmd_set(udev, &sc, 5, 0);
+                scsi_cmd_set(udev, &sc, 8, 32);
+                scsi_cmd_set(udev, &sc, 9, 0);
+                err = scsi_cmd_run(udev, &sc, fd, buffer, sizeof(buffer));
+                if ((err != 0)) {
+                        cd_media = 0;
+                        info_scsi_cmd_err(udev, "READ FIRST 32 BLOCKS", err);
+                        return -1;
+                }
+
+                /* if any non-zero data is found in sector 16 (iso and udf) or
+                 * eventually 0 (fat32 boot sector, ext2 superblock, etc), disc
+                 * is assumed non-blank */
+                result = 0;
+
+                for (block = 32768; block >= 0 && !result; block -= 32768) {
+                        offset = block;
+                        while (offset < (block + 2048) && !result) {
+                                result = buffer [offset];
+                                offset++;
+                        }
+                }
+
+                if (!result) {
+                        cd_media_state = media_status[0];
+                        info(udev, "no data in blocks 0 or 16, assuming blank\n");
+                } else {
+                        info(udev, "data in blocks 0 or 16, assuming complete\n");
+                }
+        }
+
+determined:
+        /* "other" is e. g. DVD-RAM, can't append sessions there; DVDs in
+         * restricted overwrite mode can never append, only in sequential mode */
+        if ((header[2] & 3) < 2 && !cd_media_dvd_rw_ro)
+                cd_media_session_next = header[10] << 8 | header[5];
+        cd_media_session_count = header[9] << 8 | header[4];
+        cd_media_track_count = header[11] << 8 | header[6];
+
+        return 0;
+}
+
+static int cd_media_toc(struct udev *udev, int fd)
+{
+        struct scsi_cmd sc;
+        unsigned char header[12];
+        unsigned char toc[65536];
+        unsigned int len, i, num_tracks;
+        unsigned char *p;
+        int err;
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x43);
+        scsi_cmd_set(udev, &sc, 6, 1);
+        scsi_cmd_set(udev, &sc, 8, sizeof(header) & 0xff);
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "READ TOC", err);
+                return -1;
+        }
+
+        len = (header[0] << 8 | header[1]) + 2;
+        info(udev, "READ TOC: len: %d, start track: %d, end track: %d\n", len, header[2], header[3]);
+        if (len > sizeof(toc))
+                return -1;
+        if (len < 2)
+                return -1;
+        /* 2: first track, 3: last track */
+        num_tracks = header[3] - header[2] + 1;
+
+        /* empty media has no tracks */
+        if (len < 8)
+                return 0;
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x43);
+        scsi_cmd_set(udev, &sc, 6, header[2]); /* First Track/Session Number */
+        scsi_cmd_set(udev, &sc, 7, (len >> 8) & 0xff);
+        scsi_cmd_set(udev, &sc, 8, len & 0xff);
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, toc, len);
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "READ TOC (tracks)", err);
+                return -1;
+        }
+
+        /* Take care to not iterate beyond the last valid track as specified in
+         * the TOC, but also avoid going beyond the TOC length, just in case
+         * the last track number is invalidly large */
+        for (p = toc+4, i = 4; i < len-8 && num_tracks > 0; i += 8, p += 8, --num_tracks) {
+                unsigned int block;
+                unsigned int is_data_track;
+
+                is_data_track = (p[1] & 0x04) != 0;
+
+                block = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7];
+                info(udev, "track=%u info=0x%x(%s) start_block=%u\n",
+                     p[2], p[1] & 0x0f, is_data_track ? "data":"audio", block);
+
+                if (is_data_track)
+                        cd_media_track_count_data++;
+                else
+                        cd_media_track_count_audio++;
+        }
+
+        scsi_cmd_init(udev, &sc);
+        scsi_cmd_set(udev, &sc, 0, 0x43);
+        scsi_cmd_set(udev, &sc, 2, 1); /* Session Info */
+        scsi_cmd_set(udev, &sc, 8, sizeof(header));
+        scsi_cmd_set(udev, &sc, 9, 0);
+        err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+        if ((err != 0)) {
+                info_scsi_cmd_err(udev, "READ TOC (multi session)", err);
+                return -1;
+        }
+        len = header[4+4] << 24 | header[4+5] << 16 | header[4+6] << 8 | header[4+7];
+        info(udev, "last track %u starts at block %u\n", header[4+2], len);
+        cd_media_session_last_offset = (unsigned long long int)len * 2048;
+        return 0;
+}
+
+int main(int argc, char *argv[])
+{
+        struct udev *udev;
+        static const struct option options[] = {
+                { "lock-media", no_argument, NULL, 'l' },
+                { "unlock-media", no_argument, NULL, 'u' },
+                { "eject-media", no_argument, NULL, 'e' },
+                { "debug", no_argument, NULL, 'd' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+        bool eject = false;
+        bool lock = false;
+        bool unlock = false;
+        const char *node = NULL;
+        int fd = -1;
+        int cnt;
+        int rc = 0;
+
+        udev = udev_new();
+        if (udev == NULL)
+                goto exit;
+
+        udev_log_init("cdrom_id");
+        udev_set_log_fn(udev, log_fn);
+
+        while (1) {
+                int option;
+
+                option = getopt_long(argc, argv, "deluh", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'l':
+                        lock = true;
+                        break;
+                case 'u':
+                        unlock = true;
+                        break;
+                case 'e':
+                        eject = true;
+                        break;
+                case 'd':
+                        debug = true;
+                        if (udev_get_log_priority(udev) < LOG_INFO)
+                                udev_set_log_priority(udev, LOG_INFO);
+                        break;
+                case 'h':
+                        printf("Usage: cdrom_id [options] <device>\n"
+                               "  --lock-media    lock the media (to enable eject request events)\n"
+                               "  --unlock-media  unlock the media\n"
+                               "  --eject-media   eject the media\n"
+                               "  --debug         debug to stderr\n"
+                               "  --help          print this help text\n\n");
+                        goto exit;
+                default:
+                        rc = 1;
+                        goto exit;
+                }
+        }
+
+        node = argv[optind];
+        if (!node) {
+                err(udev, "no device\n");
+                fprintf(stderr, "no device\n");
+                rc = 1;
+                goto exit;
+        }
+
+        srand((unsigned int)getpid());
+        for (cnt = 20; cnt > 0; cnt--) {
+                struct timespec duration;
+
+                fd = open(node, O_RDONLY|O_NONBLOCK|(is_mounted(node) ? 0 : O_EXCL));
+                if (fd >= 0 || errno != EBUSY)
+                        break;
+                duration.tv_sec = 0;
+                duration.tv_nsec = (100 * 1000 * 1000) + (rand() % 100 * 1000 * 1000);
+                nanosleep(&duration, NULL);
+        }
+        if (fd < 0) {
+                info(udev, "unable to open '%s'\n", node);
+                fprintf(stderr, "unable to open '%s'\n", node);
+                rc = 1;
+                goto exit;
+        }
+        info(udev, "probing: '%s'\n", node);
+
+        /* same data as original cdrom_id */
+        if (cd_capability_compat(udev, fd) < 0) {
+                rc = 1;
+                goto exit;
+        }
+
+        /* check for media - don't bail if there's no media as we still need to
+         * to read profiles */
+        cd_media_compat(udev, fd);
+
+        /* check if drive talks MMC */
+        if (cd_inquiry(udev, fd) < 0)
+                goto work;
+
+        /* read drive and possibly current profile */
+        if (cd_profiles(udev, fd) != 0)
+                goto work;
+
+        /* at this point we are guaranteed to have media in the drive - find out more about it */
+
+        /* get session/track info */
+        cd_media_toc(udev, fd);
+
+        /* get writable media state */
+        cd_media_info(udev, fd);
+
+work:
+        /* lock the media, so we enable eject button events */
+        if (lock && cd_media) {
+                info(udev, "PREVENT_ALLOW_MEDIUM_REMOVAL (lock)\n");
+                media_lock(udev, fd, true);
+        }
+
+        if (unlock && cd_media) {
+                info(udev, "PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)\n");
+                media_lock(udev, fd, false);
+        }
+
+        if (eject) {
+                info(udev, "PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)\n");
+                media_lock(udev, fd, false);
+                info(udev, "START_STOP_UNIT (eject)\n");
+                media_eject(udev, fd);
+        }
+
+        printf("ID_CDROM=1\n");
+        if (cd_cd_rom)
+                printf("ID_CDROM_CD=1\n");
+        if (cd_cd_r)
+                printf("ID_CDROM_CD_R=1\n");
+        if (cd_cd_rw)
+                printf("ID_CDROM_CD_RW=1\n");
+        if (cd_dvd_rom)
+                printf("ID_CDROM_DVD=1\n");
+        if (cd_dvd_r)
+                printf("ID_CDROM_DVD_R=1\n");
+        if (cd_dvd_rw)
+                printf("ID_CDROM_DVD_RW=1\n");
+        if (cd_dvd_ram)
+                printf("ID_CDROM_DVD_RAM=1\n");
+        if (cd_dvd_plus_r)
+                printf("ID_CDROM_DVD_PLUS_R=1\n");
+        if (cd_dvd_plus_rw)
+                printf("ID_CDROM_DVD_PLUS_RW=1\n");
+        if (cd_dvd_plus_r_dl)
+                printf("ID_CDROM_DVD_PLUS_R_DL=1\n");
+        if (cd_dvd_plus_rw_dl)
+                printf("ID_CDROM_DVD_PLUS_RW_DL=1\n");
+        if (cd_bd)
+                printf("ID_CDROM_BD=1\n");
+        if (cd_bd_r)
+                printf("ID_CDROM_BD_R=1\n");
+        if (cd_bd_re)
+                printf("ID_CDROM_BD_RE=1\n");
+        if (cd_hddvd)
+                printf("ID_CDROM_HDDVD=1\n");
+        if (cd_hddvd_r)
+                printf("ID_CDROM_HDDVD_R=1\n");
+        if (cd_hddvd_rw)
+                printf("ID_CDROM_HDDVD_RW=1\n");
+        if (cd_mo)
+                printf("ID_CDROM_MO=1\n");
+        if (cd_mrw)
+                printf("ID_CDROM_MRW=1\n");
+        if (cd_mrw_w)
+                printf("ID_CDROM_MRW_W=1\n");
+
+        if (cd_media)
+                printf("ID_CDROM_MEDIA=1\n");
+        if (cd_media_mo)
+                printf("ID_CDROM_MEDIA_MO=1\n");
+        if (cd_media_mrw)
+                printf("ID_CDROM_MEDIA_MRW=1\n");
+        if (cd_media_mrw_w)
+                printf("ID_CDROM_MEDIA_MRW_W=1\n");
+        if (cd_media_cd_rom)
+                printf("ID_CDROM_MEDIA_CD=1\n");
+        if (cd_media_cd_r)
+                printf("ID_CDROM_MEDIA_CD_R=1\n");
+        if (cd_media_cd_rw)
+                printf("ID_CDROM_MEDIA_CD_RW=1\n");
+        if (cd_media_dvd_rom)
+                printf("ID_CDROM_MEDIA_DVD=1\n");
+        if (cd_media_dvd_r)
+                printf("ID_CDROM_MEDIA_DVD_R=1\n");
+        if (cd_media_dvd_ram)
+                printf("ID_CDROM_MEDIA_DVD_RAM=1\n");
+        if (cd_media_dvd_rw)
+                printf("ID_CDROM_MEDIA_DVD_RW=1\n");
+        if (cd_media_dvd_plus_r)
+                printf("ID_CDROM_MEDIA_DVD_PLUS_R=1\n");
+        if (cd_media_dvd_plus_rw)
+                printf("ID_CDROM_MEDIA_DVD_PLUS_RW=1\n");
+        if (cd_media_dvd_plus_rw_dl)
+                printf("ID_CDROM_MEDIA_DVD_PLUS_RW_DL=1\n");
+        if (cd_media_dvd_plus_r_dl)
+                printf("ID_CDROM_MEDIA_DVD_PLUS_R_DL=1\n");
+        if (cd_media_bd)
+                printf("ID_CDROM_MEDIA_BD=1\n");
+        if (cd_media_bd_r)
+                printf("ID_CDROM_MEDIA_BD_R=1\n");
+        if (cd_media_bd_re)
+                printf("ID_CDROM_MEDIA_BD_RE=1\n");
+        if (cd_media_hddvd)
+                printf("ID_CDROM_MEDIA_HDDVD=1\n");
+        if (cd_media_hddvd_r)
+                printf("ID_CDROM_MEDIA_HDDVD_R=1\n");
+        if (cd_media_hddvd_rw)
+                printf("ID_CDROM_MEDIA_HDDVD_RW=1\n");
+
+        if (cd_media_state != NULL)
+                printf("ID_CDROM_MEDIA_STATE=%s\n", cd_media_state);
+        if (cd_media_session_next > 0)
+                printf("ID_CDROM_MEDIA_SESSION_NEXT=%d\n", cd_media_session_next);
+        if (cd_media_session_count > 0)
+                printf("ID_CDROM_MEDIA_SESSION_COUNT=%d\n", cd_media_session_count);
+        if (cd_media_session_count > 1 && cd_media_session_last_offset > 0)
+                printf("ID_CDROM_MEDIA_SESSION_LAST_OFFSET=%llu\n", cd_media_session_last_offset);
+        if (cd_media_track_count > 0)
+                printf("ID_CDROM_MEDIA_TRACK_COUNT=%d\n", cd_media_track_count);
+        if (cd_media_track_count_audio > 0)
+                printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%d\n", cd_media_track_count_audio);
+        if (cd_media_track_count_data > 0)
+                printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%d\n", cd_media_track_count_data);
+exit:
+        if (fd >= 0)
+                close(fd);
+        udev_unref(udev);
+        udev_log_close();
+        return rc;
+}
diff --git a/src/collect/collect.c b/src/collect/collect.c
new file mode 100644
index 0000000..076fe47
--- /dev/null
+++ b/src/collect/collect.c
@@ -0,0 +1,473 @@
+/*
+ * Collect variables across events.
+ *
+ * usage: collect [--add|--remove] <checkpoint> <id> <idlist>
+ *
+ * Adds ID <id> to the list governed by <checkpoint>.
+ * <id> must be part of the ID list <idlist>.
+ * If all IDs given by <idlist> are listed (ie collect has been
+ * invoked for each ID in <idlist>) collect returns 0, the
+ * number of missing IDs otherwise.
+ * A negative number is returned on error.
+ *
+ * Copyright(C) 2007, Hannes Reinecke <hare@suse.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+#define BUFSIZE                        16
+#define UDEV_ALARM_TIMEOUT        180
+
+enum collect_state {
+        STATE_NONE,
+        STATE_OLD,
+        STATE_CONFIRMED,
+};
+
+struct _mate {
+        struct udev_list_node node;
+        char *name;
+        enum collect_state state;
+};
+
+static struct udev_list_node bunch;
+static int debug;
+
+/* This can increase dynamically */
+static size_t bufsize = BUFSIZE;
+
+static struct _mate *node_to_mate(struct udev_list_node *node)
+{
+        char *mate;
+
+        mate = (char *)node;
+        mate -= offsetof(struct _mate, node);
+        return (struct _mate *)mate;
+}
+
+static void sig_alrm(int signo)
+{
+        exit(4);
+}
+
+static void usage(void)
+{
+        printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\n"
+               "\n"
+               "  Adds ID <id> to the list governed by <checkpoint>.\n"
+               "  <id> must be part of the list <idlist>.\n"
+               "  If all IDs given by <idlist> are listed (ie collect has been\n"
+               "  invoked for each ID in <idlist>) collect returns 0, the\n"
+               "  number of missing IDs otherwise.\n"
+               "  On error a negative number is returned.\n"
+               "\n");
+}
+
+/*
+ * prepare
+ *
+ * Prepares the database file
+ */
+static int prepare(char *dir, char *filename)
+{
+        struct stat statbuf;
+        char buf[512];
+        int fd;
+
+        if (stat(dir, &statbuf) < 0)
+                mkdir(dir, 0700);
+
+        sprintf(buf, "%s/%s", dir, filename);
+
+        fd = open(buf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
+        if (fd < 0)
+                fprintf(stderr, "Cannot open %s: %s\n", buf, strerror(errno));
+
+        if (lockf(fd,F_TLOCK,0) < 0) {
+                if (debug)
+                        fprintf(stderr, "Lock taken, wait for %d seconds\n", UDEV_ALARM_TIMEOUT);
+                if (errno == EAGAIN || errno == EACCES) {
+                        alarm(UDEV_ALARM_TIMEOUT);
+                        lockf(fd, F_LOCK, 0);
+                        if (debug)
+                                fprintf(stderr, "Acquired lock on %s\n", buf);
+                } else {
+                        if (debug)
+                                fprintf(stderr, "Could not get lock on %s: %s\n", buf, strerror(errno));
+                }
+        }
+
+        return fd;
+}
+
+/*
+ * Read checkpoint file
+ *
+ * Tricky reading this. We allocate a buffer twice as large
+ * as we're going to read. Then we read into the upper half
+ * of that buffer and start parsing.
+ * Once we do _not_ find end-of-work terminator (whitespace
+ * character) we move the upper half to the lower half,
+ * adjust the read pointer and read the next bit.
+ * Quite clever methinks :-)
+ * I should become a programmer ...
+ *
+ * Yes, one could have used fgets() for this. But then we'd
+ * have to use freopen etc which I found quite tedious.
+ */
+static int checkout(int fd)
+{
+        int len;
+        char *buf, *ptr, *word = NULL;
+        struct _mate *him;
+
+ restart:
+        len = bufsize >> 1;
+        buf = calloc(1,bufsize + 1);
+        if (!buf) {
+                fprintf(stderr, "Out of memory\n");
+                return -1;
+        }
+        memset(buf, ' ', bufsize);
+        ptr = buf + len;
+        while ((read(fd, buf + len, len)) > 0) {
+                while (ptr && *ptr) {
+                        word = ptr;
+                        ptr = strpbrk(word," \n\t\r");
+                        if (!ptr && word < (buf + len)) {
+                                bufsize = bufsize << 1;
+                                if (debug)
+                                        fprintf(stderr, "ID overflow, restarting with size %zi\n", bufsize);
+                                free(buf);
+                                lseek(fd, 0, SEEK_SET);
+                                goto restart;
+                        }
+                        if (ptr) {
+                                *ptr = '\0';
+                                ptr++;
+                                if (!strlen(word))
+                                        continue;
+
+                                if (debug)
+                                        fprintf(stderr, "Found word %s\n", word);
+                                him = malloc(sizeof (struct _mate));
+                                him->name = strdup(word);
+                                him->state = STATE_OLD;
+                                udev_list_node_append(&him->node, &bunch);
+                                word = NULL;
+                        }
+                }
+                memcpy(buf, buf + len, len);
+                memset(buf + len, ' ', len);
+
+                if (!ptr)
+                        ptr = word;
+                if (!ptr)
+                        break;
+                ptr -= len;
+        }
+
+        free(buf);
+        return 0;
+}
+
+/*
+ * invite
+ *
+ * Adds a new ID 'us' to the internal list,
+ * marks it as confirmed.
+ */
+static void invite(char *us)
+{
+        struct udev_list_node *him_node;
+        struct _mate *who = NULL;
+
+        if (debug)
+                fprintf(stderr, "Adding ID '%s'\n", us);
+
+        udev_list_node_foreach(him_node, &bunch) {
+                struct _mate *him = node_to_mate(him_node);
+
+                if (!strcmp(him->name, us)) {
+                        him->state = STATE_CONFIRMED;
+                        who = him;
+                }
+        }
+        if (debug && !who)
+                fprintf(stderr, "ID '%s' not in database\n", us);
+
+}
+
+/*
+ * reject
+ *
+ * Marks the ID 'us' as invalid,
+ * causing it to be removed when the
+ * list is written out.
+ */
+static void reject(char *us)
+{
+        struct udev_list_node *him_node;
+        struct _mate *who = NULL;
+
+        if (debug)
+                fprintf(stderr, "Removing ID '%s'\n", us);
+
+        udev_list_node_foreach(him_node, &bunch) {
+                struct _mate *him = node_to_mate(him_node);
+
+                if (!strcmp(him->name, us)) {
+                        him->state = STATE_NONE;
+                        who = him;
+                }
+        }
+        if (debug && !who)
+                fprintf(stderr, "ID '%s' not in database\n", us);
+}
+
+/*
+ * kickout
+ *
+ * Remove all IDs in the internal list which are not part
+ * of the list passed via the commandline.
+ */
+static void kickout(void)
+{
+        struct udev_list_node *him_node;
+        struct udev_list_node *tmp;
+
+        udev_list_node_foreach_safe(him_node, tmp, &bunch) {
+                struct _mate *him = node_to_mate(him_node);
+
+                if (him->state == STATE_OLD) {
+                        udev_list_node_remove(&him->node);
+                        free(him->name);
+                        free(him);
+                }
+        }
+}
+
+/*
+ * missing
+ *
+ * Counts all missing IDs in the internal list.
+ */
+static int missing(int fd)
+{
+        char *buf;
+        int ret = 0;
+        struct udev_list_node *him_node;
+
+        buf = malloc(bufsize);
+        if (!buf)
+                return -1;
+
+        udev_list_node_foreach(him_node, &bunch) {
+                struct _mate *him = node_to_mate(him_node);
+
+                if (him->state == STATE_NONE) {
+                        ret++;
+                } else {
+                        while (strlen(him->name)+1 >= bufsize) {
+                                char *tmpbuf;
+
+                                bufsize = bufsize << 1;
+                                tmpbuf = realloc(buf, bufsize);
+                                if (!tmpbuf) {
+                                        free(buf);
+                                        return -1;
+                                }
+                                buf = tmpbuf;
+                        }
+                        snprintf(buf, strlen(him->name)+2, "%s ", him->name);
+                        write(fd, buf, strlen(buf));
+                }
+        }
+
+        free(buf);
+        return ret;
+}
+
+/*
+ * everybody
+ *
+ * Prints out the status of the internal list.
+ */
+static void everybody(void)
+{
+        struct udev_list_node *him_node;
+        const char *state = "";
+
+        udev_list_node_foreach(him_node, &bunch) {
+                struct _mate *him = node_to_mate(him_node);
+
+                switch (him->state) {
+                case STATE_NONE:
+                        state = "none";
+                        break;
+                case STATE_OLD:
+                        state = "old";
+                        break;
+                case STATE_CONFIRMED:
+                        state = "confirmed";
+                        break;
+                }
+                fprintf(stderr, "ID: %s=%s\n", him->name, state);
+        }
+}
+
+int main(int argc, char **argv)
+{
+        struct udev *udev;
+        static const struct option options[] = {
+                { "add", no_argument, NULL, 'a' },
+                { "remove", no_argument, NULL, 'r' },
+                { "debug", no_argument, NULL, 'd' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+        int argi;
+        char *checkpoint, *us;
+        int fd;
+        int i;
+        int ret = EXIT_SUCCESS;
+        int prune = 0;
+        char tmpdir[UTIL_PATH_SIZE];
+
+        udev = udev_new();
+        if (udev == NULL) {
+                ret = EXIT_FAILURE;
+                goto exit;
+        }
+
+        while (1) {
+                int option;
+
+                option = getopt_long(argc, argv, "ardh", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'a':
+                        prune = 0;
+                        break;
+                case 'r':
+                        prune = 1;
+                        break;
+                case 'd':
+                        debug = 1;
+                        break;
+                case 'h':
+                        usage();
+                        goto exit;
+                default:
+                        ret = 1;
+                        goto exit;
+                }
+        }
+
+        argi = optind;
+        if (argi + 2 > argc) {
+                printf("Missing parameter(s)\n");
+                ret = 1;
+                goto exit;
+        }
+        checkpoint = argv[argi++];
+        us = argv[argi++];
+
+        if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
+                fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno));
+                ret = 2;
+                goto exit;
+        }
+
+        udev_list_node_init(&bunch);
+
+        if (debug)
+                fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
+
+        util_strscpyl(tmpdir, sizeof(tmpdir), udev_get_run_path(udev), "/collect", NULL);
+        fd = prepare(tmpdir, checkpoint);
+        if (fd < 0) {
+                ret = 3;
+                goto out;
+        }
+
+        if (checkout(fd) < 0) {
+                ret = 2;
+                goto out;
+        }
+
+        for (i = argi; i < argc; i++) {
+                struct udev_list_node *him_node;
+                struct _mate *who;
+
+                who = NULL;
+                udev_list_node_foreach(him_node, &bunch) {
+                        struct _mate *him = node_to_mate(him_node);
+
+                        if (!strcmp(him->name, argv[i]))
+                                who = him;
+                }
+                if (!who) {
+                        struct _mate *him;
+
+                        if (debug)
+                                fprintf(stderr, "ID %s: not in database\n", argv[i]);
+                        him = malloc(sizeof (struct _mate));
+                        him->name = malloc(strlen(argv[i]) + 1);
+                        strcpy(him->name, argv[i]);
+                        him->state = STATE_NONE;
+                        udev_list_node_append(&him->node, &bunch);
+                } else {
+                        if (debug)
+                                fprintf(stderr, "ID %s: found in database\n", argv[i]);
+                        who->state = STATE_CONFIRMED;
+                }
+        }
+
+        if (prune)
+                reject(us);
+        else
+                invite(us);
+
+        if (debug) {
+                everybody();
+                fprintf(stderr, "Prune lists\n");
+        }
+        kickout();
+
+        lseek(fd, 0, SEEK_SET);
+        ftruncate(fd, 0);
+        ret = missing(fd);
+
+        lockf(fd, F_ULOCK, 0);
+        close(fd);
+out:
+        if (debug)
+                everybody();
+        if (ret >= 0)
+                printf("COLLECT_%s=%d\n", checkpoint, ret);
+exit:
+        udev_unref(udev);
+        return ret;
+}
diff --git a/src/docs/.gitignore b/src/docs/.gitignore
new file mode 100644
index 0000000..dca700a
--- /dev/null
+++ b/src/docs/.gitignore
@@ -0,0 +1,17 @@
+libudev-overrides.txt
+html/
+tmpl/
+xml/
+*.stamp
+*.bak
+version.xml
+libudev-decl-list.txt
+libudev-decl.txt
+libudev-undeclared.txt
+libudev-undocumented.txt
+libudev-unused.txt
+libudev.args
+libudev.hierarchy
+libudev.interfaces
+libudev.prerequisites
+libudev.signals
diff --git a/src/docs/Makefile.am b/src/docs/Makefile.am
new file mode 100644
index 0000000..07d06eb
--- /dev/null
+++ b/src/docs/Makefile.am
@@ -0,0 +1,99 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libudev
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=$(top_srcdir)/src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space udev
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=--path=$(abs_srcdir) --path=$(abs_builddir)
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_srcdir)/src/libudev*.h
+CFILE_GLOB=$(top_srcdir)/src/libudev*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES= libudev-private.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = version.xml
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+#DISTCLEANFILES +=
+
+# Comment this out if you want your docs-status tested during 'make check'
+if ENABLE_GTK_DOC
+#TESTS_ENVIRONMENT = cd $(srcsrc)
+#TESTS = $(GTKDOC_CHECK)
+endif
diff --git a/src/docs/libudev-docs.xml b/src/docs/libudev-docs.xml
new file mode 100644
index 0000000..b7feb45
--- /dev/null
+++ b/src/docs/libudev-docs.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+  <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+  <bookinfo>
+    <title>libudev Reference Manual</title>
+    <releaseinfo>for libudev version &version;</releaseinfo>
+    <copyright>
+      <year>2009-2011</year>
+      <holder>Kay Sievers &lt;kay.sievers@vrfy.org&gt;</holder>
+    </copyright>
+  </bookinfo>
+
+  <chapter>
+    <title>libudev</title>
+    <xi:include href="xml/libudev.xml"/>
+    <xi:include href="xml/libudev-list.xml"/>
+    <xi:include href="xml/libudev-device.xml"/>
+    <xi:include href="xml/libudev-monitor.xml"/>
+    <xi:include href="xml/libudev-enumerate.xml"/>
+    <xi:include href="xml/libudev-queue.xml"/>
+    <xi:include href="xml/libudev-util.xml"/>
+  </chapter>
+
+  <index id="api-index-full">
+    <title>API Index</title>
+    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+  </index>
+</book>
diff --git a/src/docs/libudev-sections.txt b/src/docs/libudev-sections.txt
new file mode 100644
index 0000000..15c3e93
--- /dev/null
+++ b/src/docs/libudev-sections.txt
@@ -0,0 +1,127 @@
+<SECTION>
+<FILE>libudev</FILE>
+<TITLE>udev</TITLE>
+udev
+udev_ref
+udev_unref
+udev_new
+udev_set_log_fn
+udev_get_log_priority
+udev_set_log_priority
+udev_get_sys_path
+udev_get_dev_path
+udev_get_run_path
+udev_get_userdata
+udev_set_userdata
+</SECTION>
+
+<SECTION>
+<FILE>libudev-list</FILE>
+<TITLE>udev_list</TITLE>
+udev_list_entry
+udev_list_entry_get_next
+udev_list_entry_get_by_name
+udev_list_entry_get_name
+udev_list_entry_get_value
+udev_list_entry_foreach
+</SECTION>
+
+<SECTION>
+<FILE>libudev-device</FILE>
+<TITLE>udev_device</TITLE>
+udev_device
+udev_device_ref
+udev_device_unref
+udev_device_get_udev
+udev_device_new_from_syspath
+udev_device_new_from_devnum
+udev_device_new_from_subsystem_sysname
+udev_device_new_from_environment
+udev_device_get_parent
+udev_device_get_parent_with_subsystem_devtype
+udev_device_get_devpath
+udev_device_get_subsystem
+udev_device_get_devtype
+udev_device_get_syspath
+udev_device_get_sysname
+udev_device_get_sysnum
+udev_device_get_devnode
+udev_device_get_is_initialized
+udev_device_get_devlinks_list_entry
+udev_device_get_properties_list_entry
+udev_device_get_tags_list_entry
+udev_device_get_property_value
+udev_device_get_driver
+udev_device_get_devnum
+udev_device_get_action
+udev_device_get_sysattr_value
+udev_device_get_sysattr_list_entry
+udev_device_get_seqnum
+udev_device_get_usec_since_initialized
+udev_device_has_tag
+</SECTION>
+
+<SECTION>
+<FILE>libudev-monitor</FILE>
+<TITLE>udev_monitor</TITLE>
+udev_monitor
+udev_monitor_ref
+udev_monitor_unref
+udev_monitor_get_udev
+udev_monitor_new_from_netlink
+udev_monitor_new_from_socket
+udev_monitor_enable_receiving
+udev_monitor_set_receive_buffer_size
+udev_monitor_get_fd
+udev_monitor_receive_device
+udev_monitor_filter_add_match_subsystem_devtype
+udev_monitor_filter_add_match_tag
+udev_monitor_filter_update
+udev_monitor_filter_remove
+</SECTION>
+
+<SECTION>
+<FILE>libudev-enumerate</FILE>
+<TITLE>udev_enumerate</TITLE>
+udev_enumerate
+udev_enumerate_ref
+udev_enumerate_unref
+udev_enumerate_get_udev
+udev_enumerate_new
+udev_enumerate_add_match_subsystem
+udev_enumerate_add_nomatch_subsystem
+udev_enumerate_add_match_sysattr
+udev_enumerate_add_nomatch_sysattr
+udev_enumerate_add_match_property
+udev_enumerate_add_match_tag
+udev_enumerate_add_match_parent
+udev_enumerate_add_match_is_initialized
+udev_enumerate_add_match_sysname
+udev_enumerate_add_syspath
+udev_enumerate_scan_devices
+udev_enumerate_scan_subsystems
+udev_enumerate_get_list_entry
+</SECTION>
+
+<SECTION>
+<FILE>libudev-queue</FILE>
+<TITLE>udev_queue</TITLE>
+udev_queue
+udev_queue_ref
+udev_queue_unref
+udev_queue_get_udev
+udev_queue_new
+udev_queue_get_udev_is_active
+udev_queue_get_queue_is_empty
+udev_queue_get_seqnum_is_finished
+udev_queue_get_seqnum_sequence_is_finished
+udev_queue_get_queued_list_entry
+udev_queue_get_kernel_seqnum
+udev_queue_get_udev_seqnum
+</SECTION>
+
+<SECTION>
+<FILE>libudev-util</FILE>
+<TITLE>udev_util</TITLE>
+udev_util_encode_string
+</SECTION>
diff --git a/src/docs/libudev.types b/src/docs/libudev.types
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/docs/libudev.types
diff --git a/src/docs/version.xml.in b/src/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/src/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/src/floppy/60-floppy.rules b/src/floppy/60-floppy.rules
new file mode 100644
index 0000000..53e4a9e
--- /dev/null
+++ b/src/floppy/60-floppy.rules
@@ -0,0 +1,4 @@
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM=="block", KERNEL=="fd[0-9]", ACTION=="add", ATTRS{cmos}=="?*", ENV{CMOS_TYPE}="$attr{cmos}", \
+  RUN+="create_floppy_devices -c -t $env{CMOS_TYPE} -m %M -M 0660 -G floppy $root/%k"
diff --git a/src/floppy/create_floppy_devices.c b/src/floppy/create_floppy_devices.c
new file mode 100644
index 0000000..f71ef0d
--- /dev/null
+++ b/src/floppy/create_floppy_devices.c
@@ -0,0 +1,177 @@
+/*
+ * Create all possible floppy device based on the CMOS type.
+ * Based upon code from drivers/block/floppy.c
+ *
+ * Copyright(C) 2005, SUSE Linux Products GmbH
+ *
+ * Author: Hannes Reinecke <hare@suse.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static char *table[] = {
+        "", "d360", "h1200", "u360", "u720", "h360", "h720",
+        "u1440", "u2880", "CompaQ", "h1440", "u1680", "h410",
+        "u820", "h1476", "u1722", "h420", "u830", "h1494", "u1743",
+        "h880", "u1040", "u1120", "h1600", "u1760", "u1920",
+        "u3200", "u3520", "u3840", "u1840", "u800", "u1600",
+        NULL
+};
+
+static int t360[] = { 1, 0 };
+static int t1200[] = { 2, 5, 6, 10, 12, 14, 16, 18, 20, 23, 0 };
+static int t3in[] = { 8, 9, 26, 27, 28, 7, 11, 15, 19, 24, 25, 29, 31, 3, 4, 13, 17, 21, 22, 30, 0 };
+static int *table_sup[] = { NULL, t360, t1200, t3in+5+8, t3in+5, t3in, t3in };
+
+static void log_fn(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        vsyslog(priority, format, args);
+}
+
+int main(int argc, char **argv)
+{
+        struct udev *udev;
+        char *dev;
+        char *devname;
+        char node[64];
+        int type = 0, i, fdnum, c;
+        int major = 2, minor;
+        uid_t uid = 0;
+        gid_t gid = 0;
+        mode_t mode = 0660;
+        int create_nodes = 0;
+        int print_nodes = 0;
+        int is_err = 0;
+
+        udev = udev_new();
+        if (udev == NULL)
+                goto exit;
+
+        udev_log_init("create_floppy_devices");
+        udev_set_log_fn(udev, log_fn);
+        udev_selinux_init(udev);
+
+        while ((c = getopt(argc, argv, "cudm:U:G:M:t:")) != -1) {
+                switch (c) {
+                case 'c':
+                        create_nodes = 1;
+                        break;
+                case 'd':
+                        print_nodes = 1;
+                        break;
+                case 'U':
+                        uid = util_lookup_user(udev, optarg);
+                        break;
+                case 'G':
+                        gid = util_lookup_group(udev, optarg);
+                        break;
+                case 'M':
+                        mode = strtol(optarg, NULL, 0);
+                        mode = mode & 0666;
+                        break;
+                case 'm':
+                        major = strtol(optarg, NULL, 0);
+                        break;
+                case 't':
+                        type = strtol(optarg, NULL, 0);
+                        break;
+                default:
+                        is_err++;
+                        break;
+                }
+        }
+
+        if (is_err || optind >= argc) {
+                printf("Usage:  %s [OPTION] device\n"
+                       "  -c   create\n"
+                       "  -d   debug\n"
+                       "  -m   Major number\n"
+                       "  -t   floppy type number\n"
+                       "  -U   device node user ownership\n"
+                       "  -G   device node group owner\n"
+                       "  -M   device node mode\n"
+                       "\n", argv[0]);
+                return 1;
+        }
+
+        dev = argv[optind];
+        devname = strrchr(dev, '/');
+        if (devname != NULL)
+                devname = &devname[1];
+        else
+                devname = dev;
+        if (strncmp(devname, "fd", 2) != 0) {
+                fprintf(stderr,"Device '%s' is not a floppy device\n", dev);
+                return 1;
+        }
+
+        fdnum = strtol(&devname[2], NULL, 10);
+        if (fdnum < 0 || fdnum > 7) {
+                fprintf(stderr,"Floppy device number %d out of range (0-7)\n", fdnum);
+                return 1;
+        }
+        if (fdnum > 3)
+                fdnum += 124;
+
+        if (major < 1) {
+                fprintf(stderr,"Invalid major number %d\n", major);
+                return 1;
+        }
+
+        if (type < 0 || type >= (int) ARRAY_SIZE(table_sup)) {
+                fprintf(stderr,"Invalid CMOS type %d\n", type);
+                return 1;
+        }
+
+        if (type == 0)
+                return 0;
+
+        i = 0;
+        while (table_sup[type][i]) {
+                sprintf(node, "%s%s", dev, table[table_sup[type][i]]);
+                minor = (table_sup[type][i] << 2) + fdnum;
+                if (print_nodes)
+                        printf("%s b %.4o %d %d\n", node, mode, major, minor);
+                if (create_nodes) {
+                        unlink(node);
+                        udev_selinux_setfscreatecon(udev, node, S_IFBLK | mode);
+                        mknod(node, S_IFBLK | mode, makedev(major,minor));
+                        udev_selinux_resetfscreatecon(udev);
+                        chown(node, uid, gid);
+                        chmod(node, S_IFBLK | mode);
+                }
+                i++;
+        }
+
+        udev_selinux_exit(udev);
+        udev_unref(udev);
+        udev_log_close();
+exit:
+        return 0;
+}
diff --git a/src/gudev/.gitignore b/src/gudev/.gitignore
new file mode 100644
index 0000000..d20fa52
--- /dev/null
+++ b/src/gudev/.gitignore
@@ -0,0 +1,9 @@
+gtk-doc.make
+docs/version.xml
+gudev-1.0.pc
+gudevenumtypes.c
+gudevenumtypes.h
+gudevmarshal.c
+gudevmarshal.h
+GUdev-1.0.gir
+GUdev-1.0.typelib
diff --git a/licenses/libraries/COPYING b/src/gudev/COPYING
similarity index 99%
copy from licenses/libraries/COPYING
copy to src/gudev/COPYING
index 84bba36..da97db2 100644
--- a/licenses/libraries/COPYING
+++ b/src/gudev/COPYING
@@ -55,7 +55,7 @@
 that what they have is not the original version, so that the original
 author's reputation will not be affected by problems that might be
 introduced by others.
-
+
   Finally, software patents pose a constant threat to the existence of
 any free program.  We wish to make sure that a company cannot
 effectively restrict the users of a free program by obtaining a
@@ -111,7 +111,7 @@
 "work based on the library" and a "work that uses the library".  The
 former contains code derived from the library, whereas the latter must
 be combined with the library in order to run.
-
+
                   GNU LESSER GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
@@ -158,7 +158,7 @@
   You may charge a fee for the physical act of transferring a copy,
 and you may at your option offer warranty protection in exchange for a
 fee.
-
+
   2. You may modify your copy or copies of the Library or any portion
 of it, thus forming a work based on the Library, and copy and
 distribute such modifications or work under the terms of Section 1
@@ -216,7 +216,7 @@
 ordinary GNU General Public License has appeared, then you can specify
 that version instead if you wish.)  Do not make any other change in
 these notices.
-
+
   Once this change is made in a given copy, it is irreversible for
 that copy, so the ordinary GNU General Public License applies to all
 subsequent copies and derivative works made from that copy.
@@ -267,7 +267,7 @@
 distribute the object code for the work under the terms of Section 6.
 Any executables containing that work also fall under Section 6,
 whether or not they are linked directly with the Library itself.
-
+
   6. As an exception to the Sections above, you may also combine or
 link a "work that uses the Library" with the Library to produce a
 work containing portions of the Library, and distribute that work
@@ -329,7 +329,7 @@
 accompany the operating system.  Such a contradiction means you cannot
 use both them and the Library together in an executable that you
 distribute.
-
+
   7. You may place library facilities that are a work based on the
 Library side-by-side in a single library together with other library
 facilities not covered by this License, and distribute such a combined
@@ -370,7 +370,7 @@
 restrictions on the recipients' exercise of the rights granted herein.
 You are not responsible for enforcing compliance by third parties with
 this License.
-
+
   11. If, as a consequence of a court judgment or allegation of patent
 infringement or for any other reason (not limited to patent issues),
 conditions are imposed on you (whether by court order, agreement or
@@ -422,7 +422,7 @@
 the Free Software Foundation.  If the Library does not specify a
 license version number, you may choose any version ever published by
 the Free Software Foundation.
-
+
   14. If you wish to incorporate parts of the Library into other free
 programs whose distribution conditions are incompatible with these,
 write to the author to ask for permission.  For software which is
@@ -456,7 +456,7 @@
 DAMAGES.
 
                      END OF TERMS AND CONDITIONS
-
+
            How to Apply These Terms to Your New Libraries
 
   If you develop a new library, and you want it to be of the greatest
diff --git a/src/gudev/docs/.gitignore b/src/gudev/docs/.gitignore
new file mode 100644
index 0000000..8eada6d
--- /dev/null
+++ b/src/gudev/docs/.gitignore
@@ -0,0 +1,16 @@
+gudev-overrides.txt
+gudev-decl-list.txt
+gudev-decl.txt
+gudev-undeclared.txt
+gudev-undocumented.txt
+gudev-unused.txt
+gudev.args
+gudev.hierarchy
+gudev.interfaces
+gudev.prerequisites
+gudev.signals
+html.stamp
+html/*
+xml/*
+tmpl/*
+*.stamp
diff --git a/src/gudev/docs/Makefile.am b/src/gudev/docs/Makefile.am
new file mode 100644
index 0000000..cfe696c
--- /dev/null
+++ b/src/gudev/docs/Makefile.am
@@ -0,0 +1,106 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=gudev
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=$(top_srcdir)/src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space=g_udev
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=--path=$(abs_srcdir) --path=$(abs_builddir)
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_srcdir)/src/gudev/*.h
+CFILE_GLOB=$(top_srcdir)/src/gudev/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = version.xml
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS = \
+        $(DBUS_GLIB_CFLAGS) \
+        $(GLIB_CFLAGS) \
+        -I$(top_srcdir)/src/gudev \
+        -I$(top_builddir)/src/gudev
+
+GTKDOC_LIBS = \
+        $(GLIB_LIBS) \
+        $(top_builddir)/libgudev-1.0.la
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+#DISTCLEANFILES +=
+
+# Comment this out if you want your docs-status tested during 'make check'
+if ENABLE_GTK_DOC
+#TESTS_ENVIRONMENT = cd $(srcsrc)
+#TESTS = $(GTKDOC_CHECK)
+endif
diff --git a/src/gudev/docs/gudev-docs.xml b/src/gudev/docs/gudev-docs.xml
new file mode 100644
index 0000000..f876c3b
--- /dev/null
+++ b/src/gudev/docs/gudev-docs.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
+<!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+  <bookinfo>
+    <title>GUDev Reference Manual</title>
+    <releaseinfo>For GUdev version &version;</releaseinfo>
+    <authorgroup>
+      <author>
+        <firstname>David</firstname>
+        <surname>Zeuthen</surname>
+        <affiliation>
+          <address>
+            <email>davidz@redhat.com</email>
+          </address>
+        </affiliation>
+      </author>
+      <author>
+        <firstname>Bastien</firstname>
+        <surname>Nocera</surname>
+        <affiliation>
+          <address>
+            <email>hadess@hadess.net</email>
+          </address>
+        </affiliation>
+      </author>
+    </authorgroup>
+
+    <copyright>
+      <year>2011</year>
+      <holder>The GUDev Authors</holder>
+    </copyright>
+
+    <legalnotice>
+      <para>
+        Permission is granted to copy, distribute and/or modify this
+        document under the terms of the <citetitle>GNU Free
+        Documentation License</citetitle>, Version 1.1 or any later
+        version published by the Free Software Foundation with no
+        Invariant Sections, no Front-Cover Texts, and no Back-Cover
+        Texts. You may obtain a copy of the <citetitle>GNU Free
+        Documentation License</citetitle> from the Free Software
+        Foundation by visiting <ulink type="http"
+        url="http://www.fsf.org">their Web site</ulink> or by writing
+        to:
+
+        <address>
+          The Free Software Foundation, Inc.,
+          <street>59 Temple Place</street> - Suite 330,
+          <city>Boston</city>, <state>MA</state> <postcode>02111-1307</postcode>,
+          <country>USA</country>
+        </address>
+      </para>
+
+      <para>
+        Many of the names used by companies to distinguish their
+        products and services are claimed as trademarks. Where those
+        names appear in any freedesktop.org documentation, and those
+        trademarks are made aware to the members of the
+        freedesktop.org Project, the names have been printed in caps
+        or initial caps.
+      </para>
+    </legalnotice>
+  </bookinfo>
+
+  <reference id="ref-API">
+    <title>API Reference</title>
+    <partintro>
+      <para>
+        This part presents the class and function reference for the
+        <literal>libgudev</literal> library.
+      </para>
+    </partintro>
+    <xi:include href="xml/gudevclient.xml"/>
+    <xi:include href="xml/gudevdevice.xml"/>
+    <xi:include href="xml/gudevenumerator.xml"/>
+  </reference>
+
+  <chapter id="gudev-hierarchy">
+    <title>Object Hierarchy</title>
+      <xi:include href="xml/tree_index.sgml"/>
+  </chapter>
+  <index>
+    <title>Index</title>
+  </index>
+  <index role="165">
+    <title>Index of new symbols in 165</title>
+    <xi:include href="xml/api-index-165.xml"><xi:fallback /></xi:include>
+  </index>
+
+</book>
diff --git a/src/gudev/docs/gudev-sections.txt b/src/gudev/docs/gudev-sections.txt
new file mode 100644
index 0000000..213e1a7
--- /dev/null
+++ b/src/gudev/docs/gudev-sections.txt
@@ -0,0 +1,113 @@
+<SECTION>
+<FILE>gudevclient</FILE>
+<TITLE>GUdevClient</TITLE>
+GUdevClient
+GUdevClientClass
+GUdevDeviceType
+GUdevDeviceNumber
+g_udev_client_new
+g_udev_client_query_by_subsystem
+g_udev_client_query_by_device_number
+g_udev_client_query_by_device_file
+g_udev_client_query_by_sysfs_path
+g_udev_client_query_by_subsystem_and_name
+<SUBSECTION Standard>
+G_UDEV_CLIENT
+G_UDEV_IS_CLIENT
+G_UDEV_TYPE_CLIENT
+g_udev_client_get_type
+G_UDEV_CLIENT_CLASS
+G_UDEV_IS_CLIENT_CLASS
+G_UDEV_CLIENT_GET_CLASS
+<SUBSECTION Private>
+GUdevClientPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gudevdevice</FILE>
+<TITLE>GUdevDevice</TITLE>
+GUdevDevice
+GUdevDeviceClass
+g_udev_device_get_subsystem
+g_udev_device_get_devtype
+g_udev_device_get_name
+g_udev_device_get_number
+g_udev_device_get_sysfs_path
+g_udev_device_get_driver
+g_udev_device_get_action
+g_udev_device_get_seqnum
+g_udev_device_get_device_type
+g_udev_device_get_device_number
+g_udev_device_get_device_file
+g_udev_device_get_device_file_symlinks
+g_udev_device_get_parent
+g_udev_device_get_parent_with_subsystem
+g_udev_device_get_tags
+g_udev_device_get_is_initialized
+g_udev_device_get_usec_since_initialized
+g_udev_device_get_property_keys
+g_udev_device_has_property
+g_udev_device_get_property
+g_udev_device_get_property_as_int
+g_udev_device_get_property_as_uint64
+g_udev_device_get_property_as_double
+g_udev_device_get_property_as_boolean
+g_udev_device_get_property_as_strv
+g_udev_device_get_sysfs_attr
+g_udev_device_get_sysfs_attr_as_int
+g_udev_device_get_sysfs_attr_as_uint64
+g_udev_device_get_sysfs_attr_as_double
+g_udev_device_get_sysfs_attr_as_boolean
+g_udev_device_get_sysfs_attr_as_strv
+<SUBSECTION Standard>
+G_UDEV_DEVICE
+G_UDEV_IS_DEVICE
+G_UDEV_TYPE_DEVICE
+g_udev_device_get_type
+G_UDEV_DEVICE_CLASS
+G_UDEV_IS_DEVICE_CLASS
+G_UDEV_DEVICE_GET_CLASS
+<SUBSECTION Private>
+GUdevDevicePrivate
+</SECTION>
+
+<SECTION>
+<FILE>gudevenumerator</FILE>
+<TITLE>GUdevEnumerator</TITLE>
+GUdevEnumerator
+GUdevEnumeratorClass
+g_udev_enumerator_new
+g_udev_enumerator_add_match_subsystem
+g_udev_enumerator_add_nomatch_subsystem
+g_udev_enumerator_add_match_sysfs_attr
+g_udev_enumerator_add_nomatch_sysfs_attr
+g_udev_enumerator_add_match_property
+g_udev_enumerator_add_match_name
+g_udev_enumerator_add_match_tag
+g_udev_enumerator_add_match_is_initialized
+g_udev_enumerator_add_sysfs_path
+g_udev_enumerator_execute
+<SUBSECTION Standard>
+G_UDEV_ENUMERATOR
+G_UDEV_IS_ENUMERATOR
+G_UDEV_TYPE_ENUMERATOR
+g_udev_enumerator_get_type
+G_UDEV_ENUMERATOR_CLASS
+G_UDEV_IS_ENUMERATOR_CLASS
+G_UDEV_ENUMERATOR_GET_CLASS
+<SUBSECTION Private>
+GUdevEnumeratorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gudevmarshal</FILE>
+<SUBSECTION Private>
+g_udev_marshal_VOID__STRING_OBJECT
+</SECTION>
+
+<SECTION>
+<FILE>gudevenumtypes</FILE>
+<SUBSECTION Private>
+G_TYPE_UDEV_DEVICE_TYPE
+g_udev_device_type_get_type
+</SECTION>
diff --git a/src/gudev/docs/gudev.types b/src/gudev/docs/gudev.types
new file mode 100644
index 0000000..a89857a
--- /dev/null
+++ b/src/gudev/docs/gudev.types
@@ -0,0 +1,4 @@
+g_udev_device_type_get_type
+g_udev_device_get_type
+g_udev_client_get_type
+g_udev_enumerator_get_type
diff --git a/src/gudev/docs/version.xml.in b/src/gudev/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/src/gudev/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/src/gudev/gjs-example.js b/src/gudev/gjs-example.js
new file mode 100755
index 0000000..5586fd6
--- /dev/null
+++ b/src/gudev/gjs-example.js
@@ -0,0 +1,75 @@
+#!/usr/bin/env gjs-console
+
+// This currently depends on the following patches to gjs
+//
+// http://bugzilla.gnome.org/show_bug.cgi?id=584558
+// http://bugzilla.gnome.org/show_bug.cgi?id=584560
+// http://bugzilla.gnome.org/show_bug.cgi?id=584568
+
+const GUdev = imports.gi.GUdev;
+const Mainloop = imports.mainloop;
+
+function print_device (device) {
+  print ("  subsystem:             " + device.get_subsystem ());
+  print ("  devtype:               " + device.get_devtype ());
+  print ("  name:                  " + device.get_name ());
+  print ("  number:                " + device.get_number ());
+  print ("  sysfs_path:            " + device.get_sysfs_path ());
+  print ("  driver:                " + device.get_driver ());
+  print ("  action:                " + device.get_action ());
+  print ("  seqnum:                " + device.get_seqnum ());
+  print ("  device type:           " + device.get_device_type ());
+  print ("  device number:         " + device.get_device_number ());
+  print ("  device file:           " + device.get_device_file ());
+  print ("  device file symlinks:  " + device.get_device_file_symlinks ());
+  print ("  foo: " + device.get_sysfs_attr_as_strv ("stat"));
+  var keys = device.get_property_keys ();
+  for (var n = 0; n < keys.length; n++) {
+    print ("    " + keys[n] + "=" + device.get_property (keys[n]));
+  }
+}
+
+function on_uevent (client, action, device) {
+  print ("action " + action + " on device " + device.get_sysfs_path());
+  print_device (device);
+  print ("");
+}
+
+var client = new GUdev.Client ({subsystems: ["block", "usb/usb_interface"]});
+client.connect ("uevent", on_uevent);
+
+var block_devices = client.query_by_subsystem ("block");
+for (var n = 0; n < block_devices.length; n++) {
+  print ("block device: " + block_devices[n].get_device_file ());
+}
+
+var d;
+
+d = client.query_by_device_number (GUdev.DeviceType.BLOCK, 0x0810);
+if (d == null) {
+  print ("query_by_device_number 0x810 -> null");
+} else {
+  print ("query_by_device_number 0x810 -> " + d.get_device_file ());
+  var dd = d.get_parent_with_subsystem ("usb", null);
+  print_device (dd);
+  print ("--------------------------------------------------------------------------");
+  while (d != null) {
+    print_device (d);
+    print ("");
+    d = d.get_parent ();
+  }
+}
+
+d = client.query_by_sysfs_path ("/sys/block/sda/sda1");
+print ("query_by_sysfs_path (\"/sys/block/sda1\") -> " + d.get_device_file ());
+
+d = client.query_by_subsystem_and_name ("block", "sda2");
+print ("query_by_subsystem_and_name (\"block\", \"sda2\") -> " + d.get_device_file ());
+
+d = client.query_by_device_file ("/dev/sda");
+print ("query_by_device_file (\"/dev/sda\") -> " + d.get_device_file ());
+
+d = client.query_by_device_file ("/dev/block/8:0");
+print ("query_by_device_file (\"/dev/block/8:0\") -> " + d.get_device_file ());
+
+Mainloop.run('udev-example');
diff --git a/src/gudev/gudev-1.0.pc.in b/src/gudev/gudev-1.0.pc.in
new file mode 100644
index 0000000..058262d
--- /dev/null
+++ b/src/gudev/gudev-1.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: gudev-1.0
+Description: GObject bindings for libudev
+Version: @VERSION@
+Requires: glib-2.0, gobject-2.0
+Libs: -L${libdir} -lgudev-1.0
+Cflags: -I${includedir}/gudev-1.0
diff --git a/src/gudev/gudev.h b/src/gudev/gudev.h
new file mode 100644
index 0000000..a313460
--- /dev/null
+++ b/src/gudev/gudev.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __G_UDEV_H__
+#define __G_UDEV_H__
+
+#define _GUDEV_INSIDE_GUDEV_H 1
+#include <gudev/gudevenums.h>
+#include <gudev/gudevenumtypes.h>
+#include <gudev/gudevtypes.h>
+#include <gudev/gudevclient.h>
+#include <gudev/gudevdevice.h>
+#include <gudev/gudevenumerator.h>
+#undef _GUDEV_INSIDE_GUDEV_H
+
+#endif /* __G_UDEV_H__ */
diff --git a/src/gudev/gudevclient.c b/src/gudev/gudevclient.c
new file mode 100644
index 0000000..2b94102
--- /dev/null
+++ b/src/gudev/gudevclient.c
@@ -0,0 +1,527 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2010 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gudevclient.h"
+#include "gudevdevice.h"
+#include "gudevmarshal.h"
+#include "gudevprivate.h"
+
+/**
+ * SECTION:gudevclient
+ * @short_description: Query devices and listen to uevents
+ *
+ * #GUdevClient is used to query information about devices on a Linux
+ * system from the Linux kernel and the udev device
+ * manager.
+ *
+ * Device information is retrieved from the kernel (through the
+ * <literal>sysfs</literal> filesystem) and the udev daemon (through a
+ * <literal>tmpfs</literal> filesystem) and presented through
+ * #GUdevDevice objects. This means that no blocking IO ever happens
+ * (in both cases, we are essentially just reading data from kernel
+ * memory) and as such there are no asynchronous versions of the
+ * provided methods.
+ *
+ * To get #GUdevDevice objects, use
+ * g_udev_client_query_by_subsystem(),
+ * g_udev_client_query_by_device_number(),
+ * g_udev_client_query_by_device_file(),
+ * g_udev_client_query_by_sysfs_path(),
+ * g_udev_client_query_by_subsystem_and_name()
+ * or the #GUdevEnumerator type.
+ *
+ * To listen to uevents, connect to the #GUdevClient::uevent signal.
+ */
+
+struct _GUdevClientPrivate
+{
+  GSource *watch_source;
+  struct udev *udev;
+  struct udev_monitor *monitor;
+
+  gchar **subsystems;
+};
+
+enum
+{
+  PROP_0,
+  PROP_SUBSYSTEMS,
+};
+
+enum
+{
+  UEVENT_SIGNAL,
+  LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GUdevClient, g_udev_client, G_TYPE_OBJECT)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+monitor_event (GIOChannel *source,
+               GIOCondition condition,
+               gpointer data)
+{
+  GUdevClient *client = (GUdevClient *) data;
+  GUdevDevice *device;
+  struct udev_device *udevice;
+
+  if (client->priv->monitor == NULL)
+    goto out;
+  udevice = udev_monitor_receive_device (client->priv->monitor);
+  if (udevice == NULL)
+    goto out;
+
+  device = _g_udev_device_new (udevice);
+  udev_device_unref (udevice);
+  g_signal_emit (client,
+                 signals[UEVENT_SIGNAL],
+                 0,
+                 g_udev_device_get_action (device),
+                 device);
+  g_object_unref (device);
+
+ out:
+  return TRUE;
+}
+
+static void
+g_udev_client_finalize (GObject *object)
+{
+  GUdevClient *client = G_UDEV_CLIENT (object);
+
+  if (client->priv->watch_source != NULL)
+    {
+      g_source_destroy (client->priv->watch_source);
+      client->priv->watch_source = NULL;
+    }
+
+  if (client->priv->monitor != NULL)
+    {
+      udev_monitor_unref (client->priv->monitor);
+      client->priv->monitor = NULL;
+    }
+
+  if (client->priv->udev != NULL)
+    {
+      udev_unref (client->priv->udev);
+      client->priv->udev = NULL;
+    }
+
+  g_strfreev (client->priv->subsystems);
+
+  if (G_OBJECT_CLASS (g_udev_client_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_udev_client_parent_class)->finalize (object);
+}
+
+static void
+g_udev_client_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GUdevClient *client = G_UDEV_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_SUBSYSTEMS:
+      if (client->priv->subsystems != NULL)
+        g_strfreev (client->priv->subsystems);
+      client->priv->subsystems = g_strdupv (g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_udev_client_get_property (GObject     *object,
+                            guint        prop_id,
+                            GValue      *value,
+                            GParamSpec  *pspec)
+{
+  GUdevClient *client = G_UDEV_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_SUBSYSTEMS:
+      g_value_set_boxed (value, client->priv->subsystems);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_udev_client_constructed (GObject *object)
+{
+  GUdevClient *client = G_UDEV_CLIENT (object);
+  GIOChannel *channel;
+  guint n;
+
+  client->priv->udev = udev_new ();
+
+  /* connect to event source */
+  client->priv->monitor = udev_monitor_new_from_netlink (client->priv->udev, "udev");
+
+  //g_debug ("ss = %p", client->priv->subsystems);
+
+  if (client->priv->subsystems != NULL)
+    {
+      /* install subsystem filters to only wake up for certain events */
+      for (n = 0; client->priv->subsystems[n] != NULL; n++)
+        {
+          gchar *subsystem;
+          gchar *devtype;
+          gchar *s;
+
+          subsystem = g_strdup (client->priv->subsystems[n]);
+          devtype = NULL;
+
+          //g_debug ("s = '%s'", subsystem);
+
+          s = strstr (subsystem, "/");
+          if (s != NULL)
+            {
+              devtype = s + 1;
+              *s = '\0';
+            }
+
+          if (client->priv->monitor != NULL)
+              udev_monitor_filter_add_match_subsystem_devtype (client->priv->monitor, subsystem, devtype);
+
+          g_free (subsystem);
+        }
+
+      /* listen to events, and buffer them */
+      if (client->priv->monitor != NULL)
+        {
+          udev_monitor_enable_receiving (client->priv->monitor);
+          channel = g_io_channel_unix_new (udev_monitor_get_fd (client->priv->monitor));
+          client->priv->watch_source = g_io_create_watch (channel, G_IO_IN);
+          g_io_channel_unref (channel);
+          g_source_set_callback (client->priv->watch_source, (GSourceFunc) monitor_event, client, NULL);
+          g_source_attach (client->priv->watch_source, g_main_context_get_thread_default ());
+          g_source_unref (client->priv->watch_source);
+        }
+      else
+        {
+          client->priv->watch_source = NULL;
+        }
+    }
+
+  if (G_OBJECT_CLASS (g_udev_client_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (g_udev_client_parent_class)->constructed (object);
+}
+
+
+static void
+g_udev_client_class_init (GUdevClientClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->constructed  = g_udev_client_constructed;
+  gobject_class->set_property = g_udev_client_set_property;
+  gobject_class->get_property = g_udev_client_get_property;
+  gobject_class->finalize     = g_udev_client_finalize;
+
+  /**
+   * GUdevClient:subsystems:
+   *
+   * The subsystems to listen for uevents on.
+   *
+   * To listen for only a specific DEVTYPE for a given SUBSYSTEM, use
+   * "subsystem/devtype". For example, to only listen for uevents
+   * where SUBSYSTEM is usb and DEVTYPE is usb_interface, use
+   * "usb/usb_interface".
+   *
+   * If this property is %NULL, then no events will be reported. If
+   * it's the empty array, events from all subsystems will be
+   * reported.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_SUBSYSTEMS,
+                                   g_param_spec_boxed ("subsystems",
+                                                       "The subsystems to listen for changes on",
+                                                       "The subsystems to listen for changes on",
+                                                       G_TYPE_STRV,
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_READWRITE));
+
+  /**
+   * GUdevClient::uevent:
+   * @client: The #GUdevClient receiving the event.
+   * @action: The action for the uevent e.g. "add", "remove", "change", "move", etc.
+   * @device: Details about the #GUdevDevice the event is for.
+   *
+   * Emitted when @client receives an uevent.
+   *
+   * This signal is emitted in the
+   * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+   * of the thread that @client was created in.
+   */
+  signals[UEVENT_SIGNAL] = g_signal_new ("uevent",
+                                         G_TYPE_FROM_CLASS (klass),
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (GUdevClientClass, uevent),
+                                         NULL,
+                                         NULL,
+                                         g_udev_marshal_VOID__STRING_OBJECT,
+                                         G_TYPE_NONE,
+                                         2,
+                                         G_TYPE_STRING,
+                                         G_UDEV_TYPE_DEVICE);
+
+  g_type_class_add_private (klass, sizeof (GUdevClientPrivate));
+}
+
+static void
+g_udev_client_init (GUdevClient *client)
+{
+  client->priv = G_TYPE_INSTANCE_GET_PRIVATE (client,
+                                              G_UDEV_TYPE_CLIENT,
+                                              GUdevClientPrivate);
+}
+
+/**
+ * g_udev_client_new:
+ * @subsystems: (array zero-terminated=1) (element-type utf8) (transfer none) (allow-none): A %NULL terminated string array of subsystems to listen for uevents on, %NULL to not listen on uevents at all, or an empty array to listen to uevents on all subsystems. See the documentation for the #GUdevClient:subsystems property for details on this parameter.
+ *
+ * Constructs a #GUdevClient object that can be used to query
+ * information about devices. Connect to the #GUdevClient::uevent
+ * signal to listen for uevents. Note that signals are emitted in the
+ * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread that you call this constructor from.
+ *
+ * Returns: A new #GUdevClient object. Free with g_object_unref().
+ */
+GUdevClient *
+g_udev_client_new (const gchar * const *subsystems)
+{
+  return G_UDEV_CLIENT (g_object_new (G_UDEV_TYPE_CLIENT, "subsystems", subsystems, NULL));
+}
+
+/**
+ * g_udev_client_query_by_subsystem:
+ * @client: A #GUdevClient.
+ * @subsystem: (allow-none): The subsystem to get devices for or %NULL to get all devices.
+ *
+ * Gets all devices belonging to @subsystem.
+ *
+ * Returns: (element-type GUdevDevice) (transfer full): A list of #GUdevDevice objects. The caller should free the result by using g_object_unref() on each element in the list and then g_list_free() on the list.
+ */
+GList *
+g_udev_client_query_by_subsystem (GUdevClient  *client,
+                                  const gchar  *subsystem)
+{
+  struct udev_enumerate *enumerate;
+  struct udev_list_entry *l, *devices;
+  GList *ret;
+
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+
+  ret = NULL;
+
+  /* prepare a device scan */
+  enumerate = udev_enumerate_new (client->priv->udev);
+
+  /* filter for subsystem */
+  if (subsystem != NULL)
+    udev_enumerate_add_match_subsystem (enumerate, subsystem);
+  /* retrieve the list */
+  udev_enumerate_scan_devices (enumerate);
+
+  /* add devices to the list */
+  devices = udev_enumerate_get_list_entry (enumerate);
+  for (l = devices; l != NULL; l = udev_list_entry_get_next (l))
+    {
+      struct udev_device *udevice;
+      GUdevDevice *device;
+
+      udevice = udev_device_new_from_syspath (udev_enumerate_get_udev (enumerate),
+                                              udev_list_entry_get_name (l));
+      if (udevice == NULL)
+        continue;
+      device = _g_udev_device_new (udevice);
+      udev_device_unref (udevice);
+      ret = g_list_prepend (ret, device);
+    }
+  udev_enumerate_unref (enumerate);
+
+  ret = g_list_reverse (ret);
+
+  return ret;
+}
+
+/**
+ * g_udev_client_query_by_device_number:
+ * @client: A #GUdevClient.
+ * @type: A value from the #GUdevDeviceType enumeration.
+ * @number: A device number.
+ *
+ * Looks up a device for a type and device number.
+ *
+ * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
+ */
+GUdevDevice *
+g_udev_client_query_by_device_number (GUdevClient      *client,
+                                      GUdevDeviceType   type,
+                                      GUdevDeviceNumber number)
+{
+  struct udev_device *udevice;
+  GUdevDevice *device;
+
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+
+  device = NULL;
+  udevice = udev_device_new_from_devnum (client->priv->udev, type, number);
+
+  if (udevice == NULL)
+    goto out;
+
+  device = _g_udev_device_new (udevice);
+  udev_device_unref (udevice);
+
+ out:
+  return device;
+}
+
+/**
+ * g_udev_client_query_by_device_file:
+ * @client: A #GUdevClient.
+ * @device_file: A device file.
+ *
+ * Looks up a device for a device file.
+ *
+ * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
+ */
+GUdevDevice *
+g_udev_client_query_by_device_file (GUdevClient  *client,
+                                    const gchar  *device_file)
+{
+  struct stat stat_buf;
+  GUdevDevice *device;
+
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (device_file != NULL, NULL);
+
+  device = NULL;
+
+  if (stat (device_file, &stat_buf) != 0)
+    goto out;
+
+  if (stat_buf.st_rdev == 0)
+    goto out;
+
+  if (S_ISBLK (stat_buf.st_mode))
+    device = g_udev_client_query_by_device_number (client, G_UDEV_DEVICE_TYPE_BLOCK, stat_buf.st_rdev);
+  else if (S_ISCHR (stat_buf.st_mode))
+    device = g_udev_client_query_by_device_number (client, G_UDEV_DEVICE_TYPE_CHAR, stat_buf.st_rdev);
+
+ out:
+  return device;
+}
+
+/**
+ * g_udev_client_query_by_sysfs_path:
+ * @client: A #GUdevClient.
+ * @sysfs_path: A sysfs path.
+ *
+ * Looks up a device for a sysfs path.
+ *
+ * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
+ */
+GUdevDevice *
+g_udev_client_query_by_sysfs_path (GUdevClient  *client,
+                                   const gchar  *sysfs_path)
+{
+  struct udev_device *udevice;
+  GUdevDevice *device;
+
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (sysfs_path != NULL, NULL);
+
+  device = NULL;
+  udevice = udev_device_new_from_syspath (client->priv->udev, sysfs_path);
+  if (udevice == NULL)
+    goto out;
+
+  device = _g_udev_device_new (udevice);
+  udev_device_unref (udevice);
+
+ out:
+  return device;
+}
+
+/**
+ * g_udev_client_query_by_subsystem_and_name:
+ * @client: A #GUdevClient.
+ * @subsystem: A subsystem name.
+ * @name: The name of the device.
+ *
+ * Looks up a device for a subsystem and name.
+ *
+ * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
+ */
+GUdevDevice *
+g_udev_client_query_by_subsystem_and_name (GUdevClient  *client,
+                                           const gchar  *subsystem,
+                                           const gchar  *name)
+{
+  struct udev_device *udevice;
+  GUdevDevice *device;
+
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (subsystem != NULL, NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  device = NULL;
+  udevice = udev_device_new_from_subsystem_sysname (client->priv->udev, subsystem, name);
+  if (udevice == NULL)
+    goto out;
+
+  device = _g_udev_device_new (udevice);
+  udev_device_unref (udevice);
+
+ out:
+  return device;
+}
+
+struct udev *
+_g_udev_client_get_udev (GUdevClient *client)
+{
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+  return client->priv->udev;
+}
diff --git a/src/gudev/gudevclient.h b/src/gudev/gudevclient.h
new file mode 100644
index 0000000..b425d03
--- /dev/null
+++ b/src/gudev/gudevclient.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_CLIENT_H__
+#define __G_UDEV_CLIENT_H__
+
+#include <gudev/gudevtypes.h>
+
+G_BEGIN_DECLS
+
+#define G_UDEV_TYPE_CLIENT         (g_udev_client_get_type ())
+#define G_UDEV_CLIENT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_UDEV_TYPE_CLIENT, GUdevClient))
+#define G_UDEV_CLIENT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_CLIENT, GUdevClientClass))
+#define G_UDEV_IS_CLIENT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_CLIENT))
+#define G_UDEV_IS_CLIENT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_UDEV_TYPE_CLIENT))
+#define G_UDEV_CLIENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_UDEV_TYPE_CLIENT, GUdevClientClass))
+
+typedef struct _GUdevClientClass   GUdevClientClass;
+typedef struct _GUdevClientPrivate GUdevClientPrivate;
+
+/**
+ * GUdevClient:
+ *
+ * The #GUdevClient struct is opaque and should not be accessed directly.
+ */
+struct _GUdevClient
+{
+  GObject              parent;
+
+  /*< private >*/
+  GUdevClientPrivate *priv;
+};
+
+/**
+ * GUdevClientClass:
+ * @parent_class: Parent class.
+ * @uevent: Signal class handler for the #GUdevClient::uevent signal.
+ *
+ * Class structure for #GUdevClient.
+ */
+struct _GUdevClientClass
+{
+  GObjectClass   parent_class;
+
+  /* signals */
+  void (*uevent) (GUdevClient  *client,
+                  const gchar  *action,
+                  GUdevDevice  *device);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*reserved1) (void);
+  void (*reserved2) (void);
+  void (*reserved3) (void);
+  void (*reserved4) (void);
+  void (*reserved5) (void);
+  void (*reserved6) (void);
+  void (*reserved7) (void);
+  void (*reserved8) (void);
+};
+
+GType        g_udev_client_get_type                    (void) G_GNUC_CONST;
+GUdevClient *g_udev_client_new                         (const gchar* const *subsystems);
+GList       *g_udev_client_query_by_subsystem          (GUdevClient        *client,
+                                                        const gchar        *subsystem);
+GUdevDevice *g_udev_client_query_by_device_number      (GUdevClient        *client,
+                                                        GUdevDeviceType     type,
+                                                        GUdevDeviceNumber   number);
+GUdevDevice *g_udev_client_query_by_device_file        (GUdevClient        *client,
+                                                        const gchar        *device_file);
+GUdevDevice *g_udev_client_query_by_sysfs_path         (GUdevClient        *client,
+                                                        const gchar        *sysfs_path);
+GUdevDevice *g_udev_client_query_by_subsystem_and_name (GUdevClient        *client,
+                                                        const gchar        *subsystem,
+                                                        const gchar        *name);
+
+G_END_DECLS
+
+#endif /* __G_UDEV_CLIENT_H__ */
diff --git a/src/gudev/gudevdevice.c b/src/gudev/gudevdevice.c
new file mode 100644
index 0000000..62a26f9
--- /dev/null
+++ b/src/gudev/gudevdevice.c
@@ -0,0 +1,963 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gudevdevice.h"
+#include "gudevprivate.h"
+
+/**
+ * SECTION:gudevdevice
+ * @short_description: Get information about a device
+ *
+ * The #GUdevDevice class is used to get information about a specific
+ * device. Note that you cannot instantiate a #GUdevDevice object
+ * yourself. Instead you must use #GUdevClient to obtain #GUdevDevice
+ * objects.
+ *
+ * To get basic information about a device, use
+ * g_udev_device_get_subsystem(), g_udev_device_get_devtype(),
+ * g_udev_device_get_name(), g_udev_device_get_number(),
+ * g_udev_device_get_sysfs_path(), g_udev_device_get_driver(),
+ * g_udev_device_get_action(), g_udev_device_get_seqnum(),
+ * g_udev_device_get_device_type(), g_udev_device_get_device_number(),
+ * g_udev_device_get_device_file(),
+ * g_udev_device_get_device_file_symlinks().
+ *
+ * To navigate the device tree, use g_udev_device_get_parent() and
+ * g_udev_device_get_parent_with_subsystem().
+ *
+ * To access udev properties for the device, use
+ * g_udev_device_get_property_keys(),
+ * g_udev_device_has_property(),
+ * g_udev_device_get_property(),
+ * g_udev_device_get_property_as_int(),
+ * g_udev_device_get_property_as_uint64(),
+ * g_udev_device_get_property_as_double(),
+ * g_udev_device_get_property_as_boolean() and
+ * g_udev_device_get_property_as_strv().
+ *
+ * To access sysfs attributes for the device, use
+ * g_udev_device_get_sysfs_attr(),
+ * g_udev_device_get_sysfs_attr_as_int(),
+ * g_udev_device_get_sysfs_attr_as_uint64(),
+ * g_udev_device_get_sysfs_attr_as_double(),
+ * g_udev_device_get_sysfs_attr_as_boolean() and
+ * g_udev_device_get_sysfs_attr_as_strv().
+ *
+ * Note that all getters on #GUdevDevice are non-reffing – returned
+ * values are owned by the object, should not be freed and are only
+ * valid as long as the object is alive.
+ *
+ * By design, #GUdevDevice will not react to changes for a device – it
+ * only contains a snapshot of information when the #GUdevDevice
+ * object was created. To work with changes, you typically connect to
+ * the #GUdevClient::uevent signal on a #GUdevClient and get a new
+ * #GUdevDevice whenever an event happens.
+ */
+
+struct _GUdevDevicePrivate
+{
+  struct udev_device *udevice;
+
+  /* computed ondemand and cached */
+  gchar **device_file_symlinks;
+  gchar **property_keys;
+  gchar **tags;
+  GHashTable *prop_strvs;
+  GHashTable *sysfs_attr_strvs;
+};
+
+G_DEFINE_TYPE (GUdevDevice, g_udev_device, G_TYPE_OBJECT)
+
+static void
+g_udev_device_finalize (GObject *object)
+{
+  GUdevDevice *device = G_UDEV_DEVICE (object);
+
+  g_strfreev (device->priv->device_file_symlinks);
+  g_strfreev (device->priv->property_keys);
+  g_strfreev (device->priv->tags);
+
+  if (device->priv->udevice != NULL)
+    udev_device_unref (device->priv->udevice);
+
+  if (device->priv->prop_strvs != NULL)
+    g_hash_table_unref (device->priv->prop_strvs);
+
+  if (device->priv->sysfs_attr_strvs != NULL)
+    g_hash_table_unref (device->priv->sysfs_attr_strvs);
+
+  if (G_OBJECT_CLASS (g_udev_device_parent_class)->finalize != NULL)
+    (* G_OBJECT_CLASS (g_udev_device_parent_class)->finalize) (object);
+}
+
+static void
+g_udev_device_class_init (GUdevDeviceClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize = g_udev_device_finalize;
+
+  g_type_class_add_private (klass, sizeof (GUdevDevicePrivate));
+}
+
+static void
+g_udev_device_init (GUdevDevice *device)
+{
+  device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device,
+                                              G_UDEV_TYPE_DEVICE,
+                                              GUdevDevicePrivate);
+}
+
+
+GUdevDevice *
+_g_udev_device_new (struct udev_device *udevice)
+{
+  GUdevDevice *device;
+
+  device =  G_UDEV_DEVICE (g_object_new (G_UDEV_TYPE_DEVICE, NULL));
+  device->priv->udevice = udev_device_ref (udevice);
+
+  return device;
+}
+
+/**
+ * g_udev_device_get_subsystem:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the subsystem for @device.
+ *
+ * Returns: The subsystem for @device.
+ */
+const gchar *
+g_udev_device_get_subsystem (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_subsystem (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_devtype:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the device type for @device.
+ *
+ * Returns: The devtype for @device.
+ */
+const gchar *
+g_udev_device_get_devtype (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_devtype (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_name:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the name of @device, e.g. "sda3".
+ *
+ * Returns: The name of @device.
+ */
+const gchar *
+g_udev_device_get_name (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_sysname (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_number:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the number of @device, e.g. "3" if g_udev_device_get_name() returns "sda3".
+ *
+ * Returns: The number of @device.
+ */
+const gchar *
+g_udev_device_get_number (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_sysnum (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_sysfs_path:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the sysfs path for @device.
+ *
+ * Returns: The sysfs path for @device.
+ */
+const gchar *
+g_udev_device_get_sysfs_path (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_syspath (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_driver:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the name of the driver used for @device.
+ *
+ * Returns: The name of the driver for @device or %NULL if unknown.
+ */
+const gchar *
+g_udev_device_get_driver (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_driver (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_action:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the most recent action (e.g. "add", "remove", "change", etc.) for @device.
+ *
+ * Returns: An action string.
+ */
+const gchar *
+g_udev_device_get_action (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_action (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_seqnum:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the most recent sequence number for @device.
+ *
+ * Returns: A sequence number.
+ */
+guint64
+g_udev_device_get_seqnum (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  return udev_device_get_seqnum (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_device_type:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the type of the device file, if any, for @device.
+ *
+ * Returns: The device number for @device or #G_UDEV_DEVICE_TYPE_NONE if the device does not have a device file.
+ */
+GUdevDeviceType
+g_udev_device_get_device_type (GUdevDevice *device)
+{
+  struct stat stat_buf;
+  const gchar *device_file;
+  GUdevDeviceType type;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), G_UDEV_DEVICE_TYPE_NONE);
+
+  type = G_UDEV_DEVICE_TYPE_NONE;
+
+  /* TODO: would be better to have support for this in libudev... */
+
+  device_file = g_udev_device_get_device_file (device);
+  if (device_file == NULL)
+    goto out;
+
+  if (stat (device_file, &stat_buf) != 0)
+    goto out;
+
+  if (S_ISBLK (stat_buf.st_mode))
+    type = G_UDEV_DEVICE_TYPE_BLOCK;
+  else if (S_ISCHR (stat_buf.st_mode))
+    type = G_UDEV_DEVICE_TYPE_CHAR;
+
+ out:
+  return type;
+}
+
+/**
+ * g_udev_device_get_device_number:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the device number, if any, for @device.
+ *
+ * Returns: The device number for @device or 0 if unknown.
+ */
+GUdevDeviceNumber
+g_udev_device_get_device_number (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  return udev_device_get_devnum (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_device_file:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the device file for @device.
+ *
+ * Returns: The device file for @device or %NULL if no device file
+ * exists.
+ */
+const gchar *
+g_udev_device_get_device_file (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  return udev_device_get_devnode (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_device_file_symlinks:
+ * @device: A #GUdevDevice.
+ *
+ * Gets a list of symlinks (in <literal>/dev</literal>) that points to
+ * the device file for @device.
+ *
+ * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): A %NULL terminated string array of symlinks. This array is owned by @device and should not be freed by the caller.
+ */
+const gchar * const *
+g_udev_device_get_device_file_symlinks (GUdevDevice *device)
+{
+  struct udev_list_entry *l;
+  GPtrArray *p;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+
+  if (device->priv->device_file_symlinks != NULL)
+    goto out;
+
+  p = g_ptr_array_new ();
+  for (l = udev_device_get_devlinks_list_entry (device->priv->udevice); l != NULL; l = udev_list_entry_get_next (l))
+    {
+      g_ptr_array_add (p, g_strdup (udev_list_entry_get_name (l)));
+    }
+  g_ptr_array_add (p, NULL);
+  device->priv->device_file_symlinks = (gchar **) g_ptr_array_free (p, FALSE);
+
+ out:
+  return (const gchar * const *) device->priv->device_file_symlinks;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_udev_device_get_parent:
+ * @device: A #GUdevDevice.
+ *
+ * Gets the immediate parent of @device, if any.
+ *
+ * Returns: (transfer full): A #GUdevDevice or %NULL if @device has no parent. Free with g_object_unref().
+ */
+GUdevDevice *
+g_udev_device_get_parent (GUdevDevice  *device)
+{
+  GUdevDevice *ret;
+  struct udev_device *udevice;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+
+  ret = NULL;
+
+  udevice = udev_device_get_parent (device->priv->udevice);
+  if (udevice == NULL)
+    goto out;
+
+  ret = _g_udev_device_new (udevice);
+
+ out:
+  return ret;
+}
+
+/**
+ * g_udev_device_get_parent_with_subsystem:
+ * @device: A #GUdevDevice.
+ * @subsystem: The subsystem of the parent to get.
+ * @devtype: (allow-none): The devtype of the parent to get or %NULL.
+ *
+ * Walks up the chain of parents of @device and returns the first
+ * device encountered where @subsystem and @devtype matches, if any.
+ *
+ * Returns: (transfer full): A #GUdevDevice or %NULL if @device has no parent with @subsystem and @devtype. Free with g_object_unref().
+ */
+GUdevDevice *
+g_udev_device_get_parent_with_subsystem (GUdevDevice  *device,
+                                         const gchar  *subsystem,
+                                         const gchar  *devtype)
+{
+  GUdevDevice *ret;
+  struct udev_device *udevice;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (subsystem != NULL, NULL);
+
+  ret = NULL;
+
+  udevice = udev_device_get_parent_with_subsystem_devtype (device->priv->udevice,
+                                                           subsystem,
+                                                           devtype);
+  if (udevice == NULL)
+    goto out;
+
+  ret = _g_udev_device_new (udevice);
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_udev_device_get_property_keys:
+ * @device: A #GUdevDevice.
+ *
+ * Gets all keys for properties on @device.
+ *
+ * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): A %NULL terminated string array of property keys. This array is owned by @device and should not be freed by the caller.
+ */
+const gchar* const *
+g_udev_device_get_property_keys (GUdevDevice *device)
+{
+  struct udev_list_entry *l;
+  GPtrArray *p;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+
+  if (device->priv->property_keys != NULL)
+    goto out;
+
+  p = g_ptr_array_new ();
+  for (l = udev_device_get_properties_list_entry (device->priv->udevice); l != NULL; l = udev_list_entry_get_next (l))
+    {
+      g_ptr_array_add (p, g_strdup (udev_list_entry_get_name (l)));
+    }
+  g_ptr_array_add (p, NULL);
+  device->priv->property_keys = (gchar **) g_ptr_array_free (p, FALSE);
+
+ out:
+  return (const gchar * const *) device->priv->property_keys;
+}
+
+
+/**
+ * g_udev_device_has_property:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Check if a the property with the given key exists.
+ *
+ * Returns: %TRUE only if the value for @key exist.
+ */
+gboolean
+g_udev_device_has_property (GUdevDevice  *device,
+                            const gchar  *key)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+  return udev_device_get_property_value (device->priv->udevice, key) != NULL;
+}
+
+/**
+ * g_udev_device_get_property:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Look up the value for @key on @device.
+ *
+ * Returns: The value for @key or %NULL if @key doesn't exist on @device. Do not free this string, it is owned by @device.
+ */
+const gchar *
+g_udev_device_get_property (GUdevDevice  *device,
+                            const gchar  *key)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+  return udev_device_get_property_value (device->priv->udevice, key);
+}
+
+/**
+ * g_udev_device_get_property_as_int:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Look up the value for @key on @device and convert it to an integer
+ * using strtol().
+ *
+ * Returns: The value for @key or 0 if @key doesn't exist or
+ * isn't an integer.
+ */
+gint
+g_udev_device_get_property_as_int (GUdevDevice  *device,
+                                   const gchar  *key)
+{
+  gint result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  g_return_val_if_fail (key != NULL, 0);
+
+  result = 0;
+  s = g_udev_device_get_property (device, key);
+  if (s == NULL)
+    goto out;
+
+  result = strtol (s, NULL, 0);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_property_as_uint64:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Look up the value for @key on @device and convert it to an unsigned
+ * 64-bit integer using g_ascii_strtoull().
+ *
+ * Returns: The value  for @key or 0 if @key doesn't  exist or isn't a
+ * #guint64.
+ */
+guint64
+g_udev_device_get_property_as_uint64 (GUdevDevice  *device,
+                                      const gchar  *key)
+{
+  guint64 result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  g_return_val_if_fail (key != NULL, 0);
+
+  result = 0;
+  s = g_udev_device_get_property (device, key);
+  if (s == NULL)
+    goto out;
+
+  result = g_ascii_strtoull (s, NULL, 0);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_property_as_double:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Look up the value for @key on @device and convert it to a double
+ * precision floating point number using strtod().
+ *
+ * Returns: The value for @key or 0.0 if @key doesn't exist or isn't a
+ * #gdouble.
+ */
+gdouble
+g_udev_device_get_property_as_double (GUdevDevice  *device,
+                                      const gchar  *key)
+{
+  gdouble result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0.0);
+  g_return_val_if_fail (key != NULL, 0.0);
+
+  result = 0.0;
+  s = g_udev_device_get_property (device, key);
+  if (s == NULL)
+    goto out;
+
+  result = strtod (s, NULL);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_property_as_boolean:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Look up the value for @key on @device and convert it to an
+ * boolean. This is done by doing a case-insensitive string comparison
+ * on the string value against "1" and "true".
+ *
+ * Returns: The value for @key or %FALSE if @key doesn't exist or
+ * isn't a #gboolean.
+ */
+gboolean
+g_udev_device_get_property_as_boolean (GUdevDevice  *device,
+                                       const gchar  *key)
+{
+  gboolean result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+
+  result = FALSE;
+  s = g_udev_device_get_property (device, key);
+  if (s == NULL)
+    goto out;
+
+  if (strcmp (s, "1") == 0 || g_ascii_strcasecmp (s, "true") == 0)
+    result = TRUE;
+ out:
+  return result;
+}
+
+static gchar **
+split_at_whitespace (const gchar *s)
+{
+  gchar **result;
+  guint n;
+  guint m;
+
+  result = g_strsplit_set (s, " \v\t\r\n", 0);
+
+  /* remove empty strings, thanks GLib */
+  for (n = 0; result[n] != NULL; n++)
+    {
+      if (strlen (result[n]) == 0)
+        {
+          g_free (result[n]);
+          for (m = n; result[m] != NULL; m++)
+            result[m] = result[m + 1];
+          n--;
+        }
+    }
+
+  return result;
+}
+
+/**
+ * g_udev_device_get_property_as_strv:
+ * @device: A #GUdevDevice.
+ * @key: Name of property.
+ *
+ * Look up the value for @key on @device and return the result of
+ * splitting it into non-empty tokens split at white space (only space
+ * (' '), form-feed ('\f'), newline ('\n'), carriage return ('\r'),
+ * horizontal tab ('\t'), and vertical tab ('\v') are considered; the
+ * locale is not taken into account).
+ *
+ * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): The value of @key on @device split into tokens or %NULL if @key doesn't exist. This array is owned by @device and should not be freed by the caller.
+ */
+const gchar* const *
+g_udev_device_get_property_as_strv (GUdevDevice  *device,
+                                    const gchar  *key)
+{
+  gchar **result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  if (device->priv->prop_strvs != NULL)
+    {
+      result = g_hash_table_lookup (device->priv->prop_strvs, key);
+      if (result != NULL)
+        goto out;
+    }
+
+  result = NULL;
+  s = g_udev_device_get_property (device, key);
+  if (s == NULL)
+    goto out;
+
+  result = split_at_whitespace (s);
+  if (result == NULL)
+    goto out;
+
+  if (device->priv->prop_strvs == NULL)
+    device->priv->prop_strvs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_strfreev);
+  g_hash_table_insert (device->priv->prop_strvs, g_strdup (key), result);
+
+out:
+  return (const gchar* const *) result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_udev_device_get_sysfs_attr:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device.
+ *
+ * Returns: The value of the sysfs attribute or %NULL if there is no
+ * such attribute. Do not free this string, it is owned by @device.
+ */
+const gchar *
+g_udev_device_get_sysfs_attr (GUdevDevice  *device,
+                              const gchar  *name)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+  return udev_device_get_sysattr_value (device->priv->udevice, name);
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_int:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to an integer
+ * using strtol().
+ *
+ * Returns: The value of the sysfs attribute or 0 if there is no such
+ * attribute.
+ */
+gint
+g_udev_device_get_sysfs_attr_as_int (GUdevDevice  *device,
+                                     const gchar  *name)
+{
+  gint result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  g_return_val_if_fail (name != NULL, 0);
+
+  result = 0;
+  s = g_udev_device_get_sysfs_attr (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = strtol (s, NULL, 0);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_uint64:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to an unsigned
+ * 64-bit integer using g_ascii_strtoull().
+ *
+ * Returns: The value of the sysfs attribute or 0 if there is no such
+ * attribute.
+ */
+guint64
+g_udev_device_get_sysfs_attr_as_uint64 (GUdevDevice  *device,
+                                        const gchar  *name)
+{
+  guint64 result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  g_return_val_if_fail (name != NULL, 0);
+
+  result = 0;
+  s = g_udev_device_get_sysfs_attr (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = g_ascii_strtoull (s, NULL, 0);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_double:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to a double
+ * precision floating point number using strtod().
+ *
+ * Returns: The value of the sysfs attribute or 0.0 if there is no such
+ * attribute.
+ */
+gdouble
+g_udev_device_get_sysfs_attr_as_double (GUdevDevice  *device,
+                                        const gchar  *name)
+{
+  gdouble result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0.0);
+  g_return_val_if_fail (name != NULL, 0.0);
+
+  result = 0.0;
+  s = g_udev_device_get_sysfs_attr (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = strtod (s, NULL);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_boolean:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to an
+ * boolean. This is done by doing a case-insensitive string comparison
+ * on the string value against "1" and "true".
+ *
+ * Returns: The value of the sysfs attribute or %FALSE if there is no such
+ * attribute.
+ */
+gboolean
+g_udev_device_get_sysfs_attr_as_boolean (GUdevDevice  *device,
+                                         const gchar  *name)
+{
+  gboolean result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), FALSE);
+  g_return_val_if_fail (name != NULL, FALSE);
+
+  result = FALSE;
+  s = g_udev_device_get_sysfs_attr (device, name);
+  if (s == NULL)
+    goto out;
+
+  if (strcmp (s, "1") == 0 || g_ascii_strcasecmp (s, "true") == 0)
+    result = TRUE;
+ out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_strv:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and return the result of
+ * splitting it into non-empty tokens split at white space (only space (' '),
+ * form-feed ('\f'), newline ('\n'), carriage return ('\r'), horizontal
+ * tab ('\t'), and vertical tab ('\v') are considered; the locale is
+ * not taken into account).
+ *
+ * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): The value of the sysfs attribute split into tokens or %NULL if there is no such attribute. This array is owned by @device and should not be freed by the caller.
+ */
+const gchar * const *
+g_udev_device_get_sysfs_attr_as_strv (GUdevDevice  *device,
+                                      const gchar  *name)
+{
+  gchar **result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  if (device->priv->sysfs_attr_strvs != NULL)
+    {
+      result = g_hash_table_lookup (device->priv->sysfs_attr_strvs, name);
+      if (result != NULL)
+        goto out;
+    }
+
+  result = NULL;
+  s = g_udev_device_get_sysfs_attr (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = split_at_whitespace (s);
+  if (result == NULL)
+    goto out;
+
+  if (device->priv->sysfs_attr_strvs == NULL)
+    device->priv->sysfs_attr_strvs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_strfreev);
+  g_hash_table_insert (device->priv->sysfs_attr_strvs, g_strdup (name), result);
+
+out:
+  return (const gchar* const *) result;
+}
+
+/**
+ * g_udev_device_get_tags:
+ * @device: A #GUdevDevice.
+ *
+ * Gets all tags for @device.
+ *
+ * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): A %NULL terminated string array of tags. This array is owned by @device and should not be freed by the caller.
+ *
+ * Since: 165
+ */
+const gchar* const *
+g_udev_device_get_tags (GUdevDevice  *device)
+{
+  struct udev_list_entry *l;
+  GPtrArray *p;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+
+  if (device->priv->tags != NULL)
+    goto out;
+
+  p = g_ptr_array_new ();
+  for (l = udev_device_get_tags_list_entry (device->priv->udevice); l != NULL; l = udev_list_entry_get_next (l))
+    {
+      g_ptr_array_add (p, g_strdup (udev_list_entry_get_name (l)));
+    }
+  g_ptr_array_add (p, NULL);
+  device->priv->tags = (gchar **) g_ptr_array_free (p, FALSE);
+
+ out:
+  return (const gchar * const *) device->priv->tags;
+}
+
+/**
+ * g_udev_device_get_is_initialized:
+ * @device: A #GUdevDevice.
+ *
+ * Gets whether @device has been initalized.
+ *
+ * Returns: Whether @device has been initialized.
+ *
+ * Since: 165
+ */
+gboolean
+g_udev_device_get_is_initialized (GUdevDevice  *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), FALSE);
+  return udev_device_get_is_initialized (device->priv->udevice);
+}
+
+/**
+ * g_udev_device_get_usec_since_initialized:
+ * @device: A #GUdevDevice.
+ *
+ * Gets number of micro-seconds since @device was initialized.
+ *
+ * This only works for devices with properties in the udev
+ * database. All other devices return 0.
+ *
+ * Returns: Number of micro-seconds since @device was initialized or 0 if unknown.
+ *
+ * Since: 165
+ */
+guint64
+g_udev_device_get_usec_since_initialized (GUdevDevice *device)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  return udev_device_get_usec_since_initialized (device->priv->udevice);
+}
diff --git a/src/gudev/gudevdevice.h b/src/gudev/gudevdevice.h
new file mode 100644
index 0000000..d4873ba
--- /dev/null
+++ b/src/gudev/gudevdevice.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_DEVICE_H__
+#define __G_UDEV_DEVICE_H__
+
+#include <gudev/gudevtypes.h>
+
+G_BEGIN_DECLS
+
+#define G_UDEV_TYPE_DEVICE         (g_udev_device_get_type ())
+#define G_UDEV_DEVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_UDEV_TYPE_DEVICE, GUdevDevice))
+#define G_UDEV_DEVICE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_DEVICE, GUdevDeviceClass))
+#define G_UDEV_IS_DEVICE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_DEVICE))
+#define G_UDEV_IS_DEVICE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_UDEV_TYPE_DEVICE))
+#define G_UDEV_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_UDEV_TYPE_DEVICE, GUdevDeviceClass))
+
+typedef struct _GUdevDeviceClass   GUdevDeviceClass;
+typedef struct _GUdevDevicePrivate GUdevDevicePrivate;
+
+/**
+ * GUdevDevice:
+ *
+ * The #GUdevDevice struct is opaque and should not be accessed directly.
+ */
+struct _GUdevDevice
+{
+  GObject             parent;
+
+  /*< private >*/
+  GUdevDevicePrivate *priv;
+};
+
+/**
+ * GUdevDeviceClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #GUdevDevice.
+ */
+struct _GUdevDeviceClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*reserved1) (void);
+  void (*reserved2) (void);
+  void (*reserved3) (void);
+  void (*reserved4) (void);
+  void (*reserved5) (void);
+  void (*reserved6) (void);
+  void (*reserved7) (void);
+  void (*reserved8) (void);
+};
+
+GType               g_udev_device_get_type                  (void) G_GNUC_CONST;
+gboolean            g_udev_device_get_is_initialized        (GUdevDevice  *device);
+guint64             g_udev_device_get_usec_since_initialized (GUdevDevice  *device);
+const gchar        *g_udev_device_get_subsystem             (GUdevDevice  *device);
+const gchar        *g_udev_device_get_devtype               (GUdevDevice  *device);
+const gchar        *g_udev_device_get_name                  (GUdevDevice  *device);
+const gchar        *g_udev_device_get_number                (GUdevDevice  *device);
+const gchar        *g_udev_device_get_sysfs_path            (GUdevDevice  *device);
+const gchar        *g_udev_device_get_driver                (GUdevDevice  *device);
+const gchar        *g_udev_device_get_action                (GUdevDevice  *device);
+guint64             g_udev_device_get_seqnum                (GUdevDevice  *device);
+GUdevDeviceType     g_udev_device_get_device_type           (GUdevDevice  *device);
+GUdevDeviceNumber   g_udev_device_get_device_number         (GUdevDevice  *device);
+const gchar        *g_udev_device_get_device_file           (GUdevDevice  *device);
+const gchar* const *g_udev_device_get_device_file_symlinks  (GUdevDevice  *device);
+GUdevDevice        *g_udev_device_get_parent                (GUdevDevice  *device);
+GUdevDevice        *g_udev_device_get_parent_with_subsystem (GUdevDevice  *device,
+                                                             const gchar  *subsystem,
+                                                             const gchar  *devtype);
+const gchar* const *g_udev_device_get_property_keys         (GUdevDevice  *device);
+gboolean            g_udev_device_has_property              (GUdevDevice  *device,
+                                                             const gchar  *key);
+const gchar        *g_udev_device_get_property              (GUdevDevice  *device,
+                                                             const gchar  *key);
+gint                g_udev_device_get_property_as_int       (GUdevDevice  *device,
+                                                             const gchar  *key);
+guint64             g_udev_device_get_property_as_uint64    (GUdevDevice  *device,
+                                                             const gchar  *key);
+gdouble             g_udev_device_get_property_as_double    (GUdevDevice  *device,
+                                                             const gchar  *key);
+gboolean            g_udev_device_get_property_as_boolean   (GUdevDevice  *device,
+                                                             const gchar  *key);
+const gchar* const *g_udev_device_get_property_as_strv      (GUdevDevice  *device,
+                                                             const gchar  *key);
+
+const gchar        *g_udev_device_get_sysfs_attr            (GUdevDevice  *device,
+                                                             const gchar  *name);
+gint                g_udev_device_get_sysfs_attr_as_int     (GUdevDevice  *device,
+                                                             const gchar  *name);
+guint64             g_udev_device_get_sysfs_attr_as_uint64  (GUdevDevice  *device,
+                                                             const gchar  *name);
+gdouble             g_udev_device_get_sysfs_attr_as_double  (GUdevDevice  *device,
+                                                             const gchar  *name);
+gboolean            g_udev_device_get_sysfs_attr_as_boolean (GUdevDevice  *device,
+                                                             const gchar  *name);
+const gchar* const *g_udev_device_get_sysfs_attr_as_strv    (GUdevDevice  *device,
+                                                             const gchar  *name);
+const gchar* const *g_udev_device_get_tags                  (GUdevDevice  *device);
+
+G_END_DECLS
+
+#endif /* __G_UDEV_DEVICE_H__ */
diff --git a/src/gudev/gudevenumerator.c b/src/gudev/gudevenumerator.c
new file mode 100644
index 0000000..db09074
--- /dev/null
+++ b/src/gudev/gudevenumerator.c
@@ -0,0 +1,431 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2010 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gudevclient.h"
+#include "gudevenumerator.h"
+#include "gudevdevice.h"
+#include "gudevmarshal.h"
+#include "gudevprivate.h"
+
+/**
+ * SECTION:gudevenumerator
+ * @short_description: Lookup and sort devices
+ *
+ * #GUdevEnumerator is used to lookup and sort devices.
+ *
+ * Since: 165
+ */
+
+struct _GUdevEnumeratorPrivate
+{
+  GUdevClient *client;
+  struct udev_enumerate *e;
+};
+
+enum
+{
+  PROP_0,
+  PROP_CLIENT,
+};
+
+G_DEFINE_TYPE (GUdevEnumerator, g_udev_enumerator, G_TYPE_OBJECT)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_udev_enumerator_finalize (GObject *object)
+{
+  GUdevEnumerator *enumerator = G_UDEV_ENUMERATOR (object);
+
+  if (enumerator->priv->client != NULL)
+    {
+      g_object_unref (enumerator->priv->client);
+      enumerator->priv->client = NULL;
+    }
+
+  if (enumerator->priv->e != NULL)
+    {
+      udev_enumerate_unref (enumerator->priv->e);
+      enumerator->priv->e = NULL;
+    }
+
+  if (G_OBJECT_CLASS (g_udev_enumerator_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_udev_enumerator_parent_class)->finalize (object);
+}
+
+static void
+g_udev_enumerator_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GUdevEnumerator *enumerator = G_UDEV_ENUMERATOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      if (enumerator->priv->client != NULL)
+        g_object_unref (enumerator->priv->client);
+      enumerator->priv->client = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_udev_enumerator_get_property (GObject     *object,
+                                guint        prop_id,
+                                GValue      *value,
+                                GParamSpec  *pspec)
+{
+  GUdevEnumerator *enumerator = G_UDEV_ENUMERATOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      g_value_set_object (value, enumerator->priv->client);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_udev_enumerator_constructed (GObject *object)
+{
+  GUdevEnumerator *enumerator = G_UDEV_ENUMERATOR (object);
+
+  g_assert (G_UDEV_IS_CLIENT (enumerator->priv->client));
+
+  enumerator->priv->e = udev_enumerate_new (_g_udev_client_get_udev (enumerator->priv->client));
+
+  if (G_OBJECT_CLASS (g_udev_enumerator_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (g_udev_enumerator_parent_class)->constructed (object);
+}
+
+static void
+g_udev_enumerator_class_init (GUdevEnumeratorClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize     = g_udev_enumerator_finalize;
+  gobject_class->set_property = g_udev_enumerator_set_property;
+  gobject_class->get_property = g_udev_enumerator_get_property;
+  gobject_class->constructed  = g_udev_enumerator_constructed;
+
+  /**
+   * GUdevEnumerator:client:
+   *
+   * The #GUdevClient to enumerate devices from.
+   *
+   * Since: 165
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CLIENT,
+                                   g_param_spec_object ("client",
+                                                        "The client to enumerate devices from",
+                                                        "The client to enumerate devices from",
+                                                        G_UDEV_TYPE_CLIENT,
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_READWRITE));
+
+  g_type_class_add_private (klass, sizeof (GUdevEnumeratorPrivate));
+}
+
+static void
+g_udev_enumerator_init (GUdevEnumerator *enumerator)
+{
+  enumerator->priv = G_TYPE_INSTANCE_GET_PRIVATE (enumerator,
+                                                  G_UDEV_TYPE_ENUMERATOR,
+                                                  GUdevEnumeratorPrivate);
+}
+
+/**
+ * g_udev_enumerator_new:
+ * @client: A #GUdevClient to enumerate devices from.
+ *
+ * Constructs a #GUdevEnumerator object that can be used to enumerate
+ * and sort devices. Use the add_match_*() and add_nomatch_*() methods
+ * and execute the query to get a list of devices with
+ * g_udev_enumerator_execute().
+ *
+ * Returns: A new #GUdevEnumerator object. Free with g_object_unref().
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_new (GUdevClient *client)
+{
+  g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
+  return G_UDEV_ENUMERATOR (g_object_new (G_UDEV_TYPE_ENUMERATOR, "client", client, NULL));
+}
+
+
+/**
+ * g_udev_enumerator_add_match_subsystem:
+ * @enumerator: A #GUdevEnumerator.
+ * @subsystem: Wildcard for subsystem name e.g. 'scsi' or 'a*'.
+ *
+ * All returned devices will match the given @subsystem.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_match_subsystem (GUdevEnumerator  *enumerator,
+                                       const gchar      *subsystem)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (subsystem != NULL, NULL);
+  udev_enumerate_add_match_subsystem (enumerator->priv->e, subsystem);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_nomatch_subsystem:
+ * @enumerator: A #GUdevEnumerator.
+ * @subsystem: Wildcard for subsystem name e.g. 'scsi' or 'a*'.
+ *
+ * All returned devices will not match the given @subsystem.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_nomatch_subsystem (GUdevEnumerator  *enumerator,
+                                         const gchar      *subsystem)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (subsystem != NULL, NULL);
+  udev_enumerate_add_nomatch_subsystem (enumerator->priv->e, subsystem);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_match_sysfs_attr:
+ * @enumerator: A #GUdevEnumerator.
+ * @name: Wildcard filter for sysfs attribute key.
+ * @value: Wildcard filter for sysfs attribute value.
+ *
+ * All returned devices will have a sysfs attribute matching the given @name and @value.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_match_sysfs_attr (GUdevEnumerator  *enumerator,
+                                        const gchar      *name,
+                                        const gchar      *value)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+  g_return_val_if_fail (value != NULL, NULL);
+  udev_enumerate_add_match_sysattr (enumerator->priv->e, name, value);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_nomatch_sysfs_attr:
+ * @enumerator: A #GUdevEnumerator.
+ * @name: Wildcard filter for sysfs attribute key.
+ * @value: Wildcard filter for sysfs attribute value.
+ *
+ * All returned devices will not have a sysfs attribute matching the given @name and @value.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_nomatch_sysfs_attr (GUdevEnumerator  *enumerator,
+                                          const gchar      *name,
+                                          const gchar      *value)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+  g_return_val_if_fail (value != NULL, NULL);
+  udev_enumerate_add_nomatch_sysattr (enumerator->priv->e, name, value);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_match_property:
+ * @enumerator: A #GUdevEnumerator.
+ * @name: Wildcard filter for property name.
+ * @value: Wildcard filter for property value.
+ *
+ * All returned devices will have a property matching the given @name and @value.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_match_property (GUdevEnumerator  *enumerator,
+                                      const gchar      *name,
+                                      const gchar      *value)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+  g_return_val_if_fail (value != NULL, NULL);
+  udev_enumerate_add_match_property (enumerator->priv->e, name, value);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_match_name:
+ * @enumerator: A #GUdevEnumerator.
+ * @name: Wildcard filter for kernel name e.g. "sda*".
+ *
+ * All returned devices will match the given @name.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_match_name (GUdevEnumerator  *enumerator,
+                                  const gchar      *name)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+  udev_enumerate_add_match_sysname (enumerator->priv->e, name);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_sysfs_path:
+ * @enumerator: A #GUdevEnumerator.
+ * @sysfs_path: A sysfs path, e.g. "/sys/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda"
+ *
+ * Add a device to the list of devices, to retrieve it back sorted in dependency order.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_sysfs_path (GUdevEnumerator  *enumerator,
+                                  const gchar      *sysfs_path)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (sysfs_path != NULL, NULL);
+  udev_enumerate_add_syspath (enumerator->priv->e, sysfs_path);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_match_tag:
+ * @enumerator: A #GUdevEnumerator.
+ * @tag: A udev tag e.g. "udev-acl".
+ *
+ * All returned devices will match the given @tag.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_match_tag (GUdevEnumerator  *enumerator,
+                                 const gchar      *tag)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  g_return_val_if_fail (tag != NULL, NULL);
+  udev_enumerate_add_match_tag (enumerator->priv->e, tag);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_add_match_is_initialized:
+ * @enumerator: A #GUdevEnumerator.
+ *
+ * All returned devices will be initialized.
+ *
+ * Returns: (transfer none): The passed in @enumerator.
+ *
+ * Since: 165
+ */
+GUdevEnumerator *
+g_udev_enumerator_add_match_is_initialized (GUdevEnumerator  *enumerator)
+{
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+  udev_enumerate_add_match_is_initialized (enumerator->priv->e);
+  return enumerator;
+}
+
+/**
+ * g_udev_enumerator_execute:
+ * @enumerator: A #GUdevEnumerator.
+ *
+ * Executes the query in @enumerator.
+ *
+ * Returns: (element-type GUdevDevice) (transfer full): A list of #GUdevDevice objects. The caller should free the result by using g_object_unref() on each element in the list and then g_list_free() on the list.
+ *
+ * Since: 165
+ */
+GList *
+g_udev_enumerator_execute (GUdevEnumerator  *enumerator)
+{
+  GList *ret;
+  struct udev_list_entry *l, *devices;
+
+  g_return_val_if_fail (G_UDEV_IS_ENUMERATOR (enumerator), NULL);
+
+  ret = NULL;
+
+  /* retrieve the list */
+  udev_enumerate_scan_devices (enumerator->priv->e);
+
+  devices = udev_enumerate_get_list_entry (enumerator->priv->e);
+  for (l = devices; l != NULL; l = udev_list_entry_get_next (l))
+    {
+      struct udev_device *udevice;
+      GUdevDevice *device;
+
+      udevice = udev_device_new_from_syspath (udev_enumerate_get_udev (enumerator->priv->e),
+                                              udev_list_entry_get_name (l));
+      if (udevice == NULL)
+        continue;
+
+      device = _g_udev_device_new (udevice);
+      udev_device_unref (udevice);
+      ret = g_list_prepend (ret, device);
+    }
+
+  ret = g_list_reverse (ret);
+
+  return ret;
+}
diff --git a/src/gudev/gudevenumerator.h b/src/gudev/gudevenumerator.h
new file mode 100644
index 0000000..3fddccf
--- /dev/null
+++ b/src/gudev/gudevenumerator.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2010 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_ENUMERATOR_H__
+#define __G_UDEV_ENUMERATOR_H__
+
+#include <gudev/gudevtypes.h>
+
+G_BEGIN_DECLS
+
+#define G_UDEV_TYPE_ENUMERATOR         (g_udev_enumerator_get_type ())
+#define G_UDEV_ENUMERATOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_UDEV_TYPE_ENUMERATOR, GUdevEnumerator))
+#define G_UDEV_ENUMERATOR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_ENUMERATOR, GUdevEnumeratorClass))
+#define G_UDEV_IS_ENUMERATOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_ENUMERATOR))
+#define G_UDEV_IS_ENUMERATOR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_UDEV_TYPE_ENUMERATOR))
+#define G_UDEV_ENUMERATOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_UDEV_TYPE_ENUMERATOR, GUdevEnumeratorClass))
+
+typedef struct _GUdevEnumeratorClass   GUdevEnumeratorClass;
+typedef struct _GUdevEnumeratorPrivate GUdevEnumeratorPrivate;
+
+/**
+ * GUdevEnumerator:
+ *
+ * The #GUdevEnumerator struct is opaque and should not be accessed directly.
+ *
+ * Since: 165
+ */
+struct _GUdevEnumerator
+{
+  GObject              parent;
+
+  /*< private >*/
+  GUdevEnumeratorPrivate *priv;
+};
+
+/**
+ * GUdevEnumeratorClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #GUdevEnumerator.
+ *
+ * Since: 165
+ */
+struct _GUdevEnumeratorClass
+{
+  GObjectClass   parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*reserved1) (void);
+  void (*reserved2) (void);
+  void (*reserved3) (void);
+  void (*reserved4) (void);
+  void (*reserved5) (void);
+  void (*reserved6) (void);
+  void (*reserved7) (void);
+  void (*reserved8) (void);
+};
+
+GType            g_udev_enumerator_get_type                     (void) G_GNUC_CONST;
+GUdevEnumerator *g_udev_enumerator_new                          (GUdevClient      *client);
+GUdevEnumerator *g_udev_enumerator_add_match_subsystem          (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *subsystem);
+GUdevEnumerator *g_udev_enumerator_add_nomatch_subsystem        (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *subsystem);
+GUdevEnumerator *g_udev_enumerator_add_match_sysfs_attr         (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *name,
+                                                                 const gchar      *value);
+GUdevEnumerator *g_udev_enumerator_add_nomatch_sysfs_attr       (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *name,
+                                                                 const gchar      *value);
+GUdevEnumerator *g_udev_enumerator_add_match_property           (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *name,
+                                                                 const gchar      *value);
+GUdevEnumerator *g_udev_enumerator_add_match_name               (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *name);
+GUdevEnumerator *g_udev_enumerator_add_match_tag                (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *tag);
+GUdevEnumerator *g_udev_enumerator_add_match_is_initialized     (GUdevEnumerator  *enumerator);
+GUdevEnumerator *g_udev_enumerator_add_sysfs_path               (GUdevEnumerator  *enumerator,
+                                                                 const gchar      *sysfs_path);
+GList           *g_udev_enumerator_execute                      (GUdevEnumerator  *enumerator);
+
+G_END_DECLS
+
+#endif /* __G_UDEV_ENUMERATOR_H__ */
diff --git a/src/gudev/gudevenums.h b/src/gudev/gudevenums.h
new file mode 100644
index 0000000..c3a0aa8
--- /dev/null
+++ b/src/gudev/gudevenums.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_ENUMS_H__
+#define __G_UDEV_ENUMS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GUdevDeviceType:
+ * @G_UDEV_DEVICE_TYPE_NONE: Device does not have a device file.
+ * @G_UDEV_DEVICE_TYPE_BLOCK: Device is a block device.
+ * @G_UDEV_DEVICE_TYPE_CHAR: Device is a character device.
+ *
+ * Enumeration used to specify a the type of a device.
+ */
+typedef enum
+{
+  G_UDEV_DEVICE_TYPE_NONE = 0,
+  G_UDEV_DEVICE_TYPE_BLOCK = 'b',
+  G_UDEV_DEVICE_TYPE_CHAR = 'c',
+} GUdevDeviceType;
+
+G_END_DECLS
+
+#endif /* __G_UDEV_ENUMS_H__ */
diff --git a/src/gudev/gudevenumtypes.c.template b/src/gudev/gudevenumtypes.c.template
new file mode 100644
index 0000000..fc30b39
--- /dev/null
+++ b/src/gudev/gudevenumtypes.c.template
@@ -0,0 +1,39 @@
+/*** BEGIN file-header ***/
+#include <gudev.h>
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+        { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+/*** END file-tail ***/
diff --git a/src/gudev/gudevenumtypes.h.template b/src/gudev/gudevenumtypes.h.template
new file mode 100644
index 0000000..d0ab339
--- /dev/null
+++ b/src/gudev/gudevenumtypes.h.template
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __GUDEV_ENUM_TYPES_H__
+#define __GUDEV_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __GUDEV_ENUM_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/src/gudev/gudevmarshal.list b/src/gudev/gudevmarshal.list
new file mode 100644
index 0000000..7e66599
--- /dev/null
+++ b/src/gudev/gudevmarshal.list
@@ -0,0 +1 @@
+VOID:STRING,OBJECT
diff --git a/src/gudev/gudevprivate.h b/src/gudev/gudevprivate.h
new file mode 100644
index 0000000..8866f52
--- /dev/null
+++ b/src/gudev/gudevprivate.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_PRIVATE_H__
+#define __G_UDEV_PRIVATE_H__
+
+#include <gudev/gudevtypes.h>
+
+#include <libudev.h>
+
+G_BEGIN_DECLS
+
+GUdevDevice *
+_g_udev_device_new (struct udev_device *udevice);
+
+struct udev *_g_udev_client_get_udev (GUdevClient *client);
+
+G_END_DECLS
+
+#endif /* __G_UDEV_PRIVATE_H__ */
diff --git a/src/gudev/gudevtypes.h b/src/gudev/gudevtypes.h
new file mode 100644
index 0000000..8884827
--- /dev/null
+++ b/src/gudev/gudevtypes.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_TYPES_H__
+#define __G_UDEV_TYPES_H__
+
+#include <gudev/gudevenums.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GUdevClient GUdevClient;
+typedef struct _GUdevDevice GUdevDevice;
+typedef struct _GUdevEnumerator GUdevEnumerator;
+
+/**
+ * GUdevDeviceNumber:
+ *
+ * Corresponds to the standard #dev_t type as defined by POSIX (Until
+ * bug 584517 is resolved this work-around is needed).
+ */
+#ifdef _GUDEV_WORK_AROUND_DEV_T_BUG
+typedef guint64 GUdevDeviceNumber; /* __UQUAD_TYPE */
+#else
+typedef dev_t GUdevDeviceNumber;
+#endif
+
+G_END_DECLS
+
+#endif /* __G_UDEV_TYPES_H__ */
diff --git a/src/gudev/seed-example-enum.js b/src/gudev/seed-example-enum.js
new file mode 100755
index 0000000..66206ad
--- /dev/null
+++ b/src/gudev/seed-example-enum.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env seed
+
+const GLib = imports.gi.GLib;
+const GUdev = imports.gi.GUdev;
+
+function print_device(device) {
+  print("  initialized:            " + device.get_is_initialized());
+  print("  usec since initialized: " + device.get_usec_since_initialized());
+  print("  subsystem:              " + device.get_subsystem());
+  print("  devtype:                " + device.get_devtype());
+  print("  name:                   " + device.get_name());
+  print("  number:                 " + device.get_number());
+  print("  sysfs_path:             " + device.get_sysfs_path());
+  print("  driver:                 " + device.get_driver());
+  print("  action:                 " + device.get_action());
+  print("  seqnum:                 " + device.get_seqnum());
+  print("  device type:            " + device.get_device_type());
+  print("  device number:          " + device.get_device_number());
+  print("  device file:            " + device.get_device_file());
+  print("  device file symlinks:   " + device.get_device_file_symlinks());
+  print("  tags:                   " + device.get_tags());
+  var keys = device.get_property_keys();
+  for (var n = 0; n < keys.length; n++) {
+    print("    " + keys[n] + "=" + device.get_property(keys[n]));
+  }
+}
+
+var client = new GUdev.Client({subsystems: []});
+var enumerator = new GUdev.Enumerator({client: client});
+enumerator.add_match_subsystem('b*')
+
+var devices = enumerator.execute();
+
+for (var n=0; n < devices.length; n++) {
+    var device = devices[n];
+    print_device(device);
+    print("");
+}
diff --git a/src/gudev/seed-example.js b/src/gudev/seed-example.js
new file mode 100755
index 0000000..e2ac324
--- /dev/null
+++ b/src/gudev/seed-example.js
@@ -0,0 +1,72 @@
+#!/usr/bin/env seed
+
+// seed example
+
+const GLib = imports.gi.GLib;
+const GUdev = imports.gi.GUdev;
+
+function print_device (device) {
+  print ("  subsystem:             " + device.get_subsystem ());
+  print ("  devtype:               " + device.get_devtype ());
+  print ("  name:                  " + device.get_name ());
+  print ("  number:                " + device.get_number ());
+  print ("  sysfs_path:            " + device.get_sysfs_path ());
+  print ("  driver:                " + device.get_driver ());
+  print ("  action:                " + device.get_action ());
+  print ("  seqnum:                " + device.get_seqnum ());
+  print ("  device type:           " + device.get_device_type ());
+  print ("  device number:         " + device.get_device_number ());
+  print ("  device file:           " + device.get_device_file ());
+  print ("  device file symlinks:  " + device.get_device_file_symlinks ());
+  print ("  foo: " + device.get_sysfs_attr_as_strv ("stat"));
+  var keys = device.get_property_keys ();
+  for (var n = 0; n < keys.length; n++) {
+    print ("    " + keys[n] + "=" + device.get_property (keys[n]));
+  }
+}
+
+function on_uevent (client, action, device) {
+  print ("action " + action + " on device " + device.get_sysfs_path());
+  print_device (device);
+  print ("");
+}
+
+var client = new GUdev.Client ({subsystems: ["block", "usb/usb_interface"]});
+client.signal.connect ("uevent", on_uevent);
+
+var block_devices = client.query_by_subsystem ("block");
+for (var n = 0; n < block_devices.length; n++) {
+  print ("block device: " + block_devices[n].get_device_file ());
+}
+
+var d;
+
+d = client.query_by_device_number (GUdev.DeviceType.BLOCK, 0x0810);
+if (d == null) {
+  print ("query_by_device_number 0x810 -> null");
+} else {
+  print ("query_by_device_number 0x810 -> " + d.get_device_file ());
+  dd = d.get_parent_with_subsystem ("usb", null);
+  print_device (dd);
+  print ("--------------------------------------------------------------------------");
+  while (d != null) {
+    print_device (d);
+    print ("");
+    d = d.get_parent ();
+  }
+}
+
+d = client.query_by_sysfs_path ("/sys/block/sda/sda1");
+print ("query_by_sysfs_path (\"/sys/block/sda1\") -> " + d.get_device_file ());
+
+d = client.query_by_subsystem_and_name ("block", "sda2");
+print ("query_by_subsystem_and_name (\"block\", \"sda2\") -> " + d.get_device_file ());
+
+d = client.query_by_device_file ("/dev/sda");
+print ("query_by_device_file (\"/dev/sda\") -> " + d.get_device_file ());
+
+d = client.query_by_device_file ("/dev/block/8:0");
+print ("query_by_device_file (\"/dev/block/8:0\") -> " + d.get_device_file ());
+
+var mainloop = GLib.main_loop_new ();
+GLib.main_loop_run (mainloop);
diff --git a/src/keymap/.gitignore b/src/keymap/.gitignore
new file mode 100644
index 0000000..4567584
--- /dev/null
+++ b/src/keymap/.gitignore
@@ -0,0 +1,5 @@
+keyboard-force-release.sh
+keys-from-name.gperf
+keys-from-name.h
+keys-to-name.h
+keys.txt
diff --git a/lib/udev/rules.d/95-keyboard-force-release.rules b/src/keymap/95-keyboard-force-release.rules
similarity index 92%
rename from lib/udev/rules.d/95-keyboard-force-release.rules
rename to src/keymap/95-keyboard-force-release.rules
index 63cf67f..03d56e8 100644
--- a/lib/udev/rules.d/95-keyboard-force-release.rules
+++ b/src/keymap/95-keyboard-force-release.rules
@@ -5,10 +5,10 @@
 # The atkbd driver has a quirk handler for generating synthetic
 # release events, which can be configured via sysfs since 2.6.32.
 # Simply add a file with a list of scancodes for your laptop model
-# in /lib/udev/keymaps, and add a rule here.
+# in /usr/lib/udev/keymaps, and add a rule here.
 # If the hotkeys also need a keymap assignment you can copy the
 # scancodes from the keymap file, otherwise you can run
-# /lib/udev/keymap -i /dev/input/eventX
+# /usr/lib/udev/keymap -i /dev/input/eventX
 # on a Linux vt to find out.
 
 ACTION=="remove", GOTO="force_release_end"
@@ -19,6 +19,7 @@
 ENV{DMI_VENDOR}="$attr{[dmi/id]sys_vendor}"
 
 ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", RUN+="keyboard-force-release.sh $devpath samsung-other"
+ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*90X3A*", RUN+="keyboard-force-release.sh $devpath samsung-90x3a"
 
 ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="Studio 1557|Studio 1558", RUN+="keyboard-force-release.sh $devpath common-volume-keys"
 ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="Latitude E*|Precision M*", RUN+="keyboard-force-release.sh $devpath dell-touchpad"
diff --git a/lib/udev/rules.d/95-keymap.rules b/src/keymap/95-keymap.rules
similarity index 92%
rename from lib/udev/rules.d/95-keymap.rules
rename to src/keymap/95-keymap.rules
index 7c2f88c..bbf311a 100644
--- a/lib/udev/rules.d/95-keymap.rules
+++ b/src/keymap/95-keymap.rules
@@ -2,7 +2,7 @@
 #
 # Key map overrides can be specified by either giving scancode/keyname pairs
 # directly as keymap arguments (if there are just one or two to change), or as
-# a file name (in /lib/udev/keymaps), which has to contain scancode/keyname
+# a file name (in /usr/lib/udev/keymaps), which has to contain scancode/keyname
 # pairs.
 
 ACTION=="remove", GOTO="keyboard_end"
@@ -79,7 +79,7 @@
 
 ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="*3000*", RUN+="keymap $name lenovo-3000"
 ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X6*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x6_tablet"
-ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X20* Tablet*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x200_tablet"
+ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X2[02]* Tablet*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x200_tablet"
 ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="*IdeaPad*", RUN+="keymap $name lenovo-ideapad"
 ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_name}=="S10-*", RUN+="keymap $name lenovo-ideapad"
 ENV{DMI_VENDOR}=="LENOVO", ATTR{[dmi/id]product_version}=="*IdeaPad Y550*", RUN+="keymap $name 0x95 media 0xA3 play"
@@ -92,6 +92,7 @@
 ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][xX]2*", RUN+="keymap $name hewlett-packard-tx2"
 ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="Presario 2100*", RUN+="keymap $name hewlett-packard-presario-2100"
 ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP G62 Notebook PC", RUN+="keymap $name 0xB2 www"
+ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP ProBook*", RUN+="keymap $name 0xF8 rfkill"
 # HP Pavillion dv6315ea has empty DMI_VENDOR
 ATTR{[dmi/id]board_vendor}=="Quanta", ATTR{[dmi/id]board_name}=="30B7", ATTR{[dmi/id]board_version}=="65.2B", RUN+="keymap $name 0x88 media" # "quick play
 
@@ -120,6 +121,7 @@
 ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*Amilo Si 1520*", RUN+="keymap $name fujitsu-amilo_si_1520"
 ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="AMILO*M*", RUN+="keymap $name 0x97 prog2 0x9F prog1"
 ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="Amilo Li 1718", RUN+="keymap $name 0xD6 wlan"
+ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="AMILO Li 2732", RUN+="keymap $name fujitsu-amilo_li_2732"
 
 ENV{DMI_VENDOR}=="LG*", ATTR{[dmi/id]product_name}=="*X110*", RUN+="keymap $name lg-x110"
 
@@ -140,6 +142,8 @@
 ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", RUN+="keymap $name samsung-other"
 ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*SX20S*", RUN+="keymap $name samsung-sx20s"
 ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="SQ1US", RUN+="keymap $name samsung-sq1us"
+ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*700Z*", RUN+="keymap $name 0xBA ejectcd 0x96 keyboardbrightnessup 0x97 keyboardbrightnessdown"
+ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*90X3A*", RUN+="keymap $name samsung-90x3a"
 
 ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="SATELLITE A100", RUN+="keymap $name toshiba-satellite_a100"
 ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite A110", RUN+="keymap $name toshiba-satellite_a110"
@@ -161,4 +165,6 @@
 
 ENV{DMI_VENDOR}=="OLPC", ATTR{[dmi/id]product_name}=="XO", RUN+="keymap $name olpc-xo"
 
+ENV{DMI_VENDOR}=="Alienware*", ATTR{[dmi/id]product_name}=="M14xR1", RUN+="keymap $name 0x8A ejectcd"
+
 LABEL="keyboard_end"
diff --git a/src/keymap/README.keymap.txt b/src/keymap/README.keymap.txt
new file mode 100644
index 0000000..52d50ed
--- /dev/null
+++ b/src/keymap/README.keymap.txt
@@ -0,0 +1,101 @@
+= The udev keymap tool =
+
+== Introduction ==
+
+This udev extension configures computer model specific key mappings. This is
+particularly necessary for the non-standard extra keys found on many laptops,
+such as "brightness up", "next song", "www browser", or "suspend". Often these
+are accessed with the Fn key.
+
+Every key produces a "scan code", which is highly vendor/model specific for the
+nonstandard keys. This tool maintains mappings for these scan codes to standard
+"key codes", which denote the "meaning" of the key. The key codes are defined
+in /usr/include/linux/input.h.
+
+If some of your keys on your keyboard are not working at all, or produce the
+wrong effect, then a very likely cause of this is that the scan code -> key
+code mapping is incorrect on your computer.
+
+== Structure ==
+
+udev-keymap consists of the following parts:
+
+ keymaps/*:: mappings of scan codes to key code names
+
+ 95-keymap.rules:: udev rules for mapping system vendor/product names and
+ input module names to one of the keymaps above
+
+ keymap:: manipulate an evdev input device:
+  * write a key map file into a device (used by udev rules)
+  * dump current scan → key code mapping
+  * interactively display scan and key codes of pressed keys
+
+ findkeyboards:: display evdev input devices which belong to actual keyboards,
+ i. e. those suitable for the keymap program
+
+ fdi2rules.py:: convert hal keymap FDIs into udev rules and key map files
+ (Please note that this is far from perfect, since the mapping between fdi and
+  udev rules is not straightforward, and impossible in some cases.)
+
+== Fixing broken keys ==
+
+In order to make a broken key work on your system and send it back to upstream
+for inclusion you need to do the following steps:
+
+ 1. Find the keyboard device.
+
+ Run /usr/lib/udev/findkeyboards. This should always give you an "AT
+ keyboard" and possibly a "module". Some laptops (notably Thinkpads, Sonys, and
+ Acers) have multimedia/function keys on a separate input device instead of the
+ primary keyboard. The keyboard device should have a name like "input/event3".
+ In the following commands, the name will be written as "input/eventX" (replace
+ X with the appropriate number).
+
+ 2. Find broken scan codes:
+
+ sudo /usr/lib/udev/keymap -i input/eventX
+
+ Press all multimedia/function keys and check if the key name that gets printed
+ out is plausible. If it is unknown or wrong, write down the scan code (looks
+ like "0x1E") and the intended functionality of this key. Look in
+ /usr/include/linux/input.h for an available KEY_XXXXX constant which most
+ closely approximates this functionality and write it down as the new key code.
+
+ For example, you might press a key labeled "web browser" which currently
+ produces "unknown". Note down this:
+
+   0x1E www # Fn+F2 web browser
+
+ Repeat that for all other keys. Write the resulting list into a file. Look at
+ /usr/lib/udev/keymaps/ for existing key map files and make sure that you use the
+ same structure.
+
+ If the key only ever works once and then your keyboard (or the entire desktop)
+ gets stuck for a long time, then it is likely that the BIOS fails to send a
+ corresponding "key release" event after the key press event. Please note down
+ this case as well, as it can be worked around in
+ /usr/lib/udev/keymaps/95-keyboard-force-release.rules .
+
+ 3. Find out your system vendor and product:
+
+ cat /sys/class/dmi/id/sys_vendor
+ cat /sys/class/dmi/id/product_name
+
+ 4. Generate a device dump with "udevadm info --export-db > /tmp/udev-db.txt".
+
+ 6. Send the system vendor/product names, the key mapping from step 2,
+ and /tmp/udev-db.txt from step 4 to the linux-hotplug@vger.kernel.org mailing
+ list, so that they can be included in the next release.
+
+For local testing, copy your map file to /usr/lib/udev/keymaps/ with an appropriate
+name, and add an appropriate udev rule to /usr/lib/udev/rules.d/95-keymap.rules:
+
+  * If you selected an "AT keyboard", add the rule to the section after
+  'LABEL="keyboard_vendorcheck"'.
+
+  * If you selected a "module", add the rule to the top section where the
+  "ThinkPad Extra Buttons" are.
+
+== Author ==
+
+keymap is written and maintained by Martin Pitt <martin.pitt@ubuntu.com>.
diff --git a/src/keymap/check-keymaps.sh b/src/keymap/check-keymaps.sh
new file mode 100755
index 0000000..405168c
--- /dev/null
+++ b/src/keymap/check-keymaps.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# check that all key names in keymaps/* are known in <linux/input.h>
+# and that all key maps listed in the rules are valid and present in
+# Makefile.am
+SRCDIR=${1:-.}
+KEYLIST=${2:-src/keymap/keys.txt}
+KEYMAPS_DIR=$SRCDIR/src/keymap/keymaps
+RULES=$SRCDIR/src/keymap/95-keymap.rules
+
+[ -e "$KEYLIST" ] || {
+        echo "need $KEYLIST please build first" >&2
+        exit 1
+}
+
+missing=$(join -v 2 <(awk '{print tolower(substr($1,5))}' $KEYLIST | sort -u) \
+                    <(grep -hv '^#' ${KEYMAPS_DIR}/*| awk '{print $2}' | sort -u))
+[ -z "$missing" ] || {
+        echo "ERROR: unknown key names in src/keymap/keymaps/*:" >&2
+        echo "$missing" >&2
+        exit 1
+}
+
+# check that all maps referred to in $RULES exist
+maps=$(sed -rn '/keymap \$name/ { s/^.*\$name ([^"[:space:]]+).*$/\1/; p }' $RULES)
+for m in $maps; do
+        # ignore inline mappings
+        [ "$m" = "${m#0x}" ] || continue
+
+        [ -e ${KEYMAPS_DIR}/$m ] || {
+                echo "ERROR: unknown map name in $RULES: $m" >&2
+                exit 1
+        }
+        grep -q "src/keymap/keymaps/$m\>" $SRCDIR/Makefile.am || {
+                echo "ERROR: map file $m is not added to Makefile.am" >&2
+                exit 1
+        }
+done
diff --git a/src/keymap/findkeyboards b/src/keymap/findkeyboards
new file mode 100755
index 0000000..9ce2742
--- /dev/null
+++ b/src/keymap/findkeyboards
@@ -0,0 +1,68 @@
+#!/bin/sh -e
+# Find "real" keyboard devices and print their device path.
+# Author: Martin Pitt <martin.pitt@ubuntu.com>
+#
+# Copyright (C) 2009, Canonical Ltd.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# returns OK if $1 contains $2
+strstr() {
+        [ "${1#*$2*}" != "$1" ]
+}
+
+# returns OK if $1 contains $2 at the beginning
+str_starts() {
+        [ "${1#$2*}" != "$1" ]
+}
+
+str_line_starts() {
+        while read a; do str_starts "$a" "$1" && return 0;done
+        return 1;
+}
+
+# print a list of input devices which are keyboard-like
+keyboard_devices() {
+        # standard AT keyboard
+        for dev in `udevadm trigger --dry-run --verbose --property-match=ID_INPUT_KEYBOARD=1`; do
+                walk=`udevadm info --attribute-walk --path=$dev`
+                env=`udevadm info --query=env --path=$dev`
+                # filter out non-event devices, such as the parent input devices which have no devnode
+                if ! echo "$env" | str_line_starts 'DEVNAME='; then
+                        continue
+                fi
+                if strstr "$walk" 'DRIVERS=="atkbd"'; then
+                        echo -n 'AT keyboard: '
+                elif echo "$env" | str_line_starts 'ID_USB_DRIVER=usbhid'; then
+                        echo -n 'USB keyboard: '
+                else
+                        echo -n 'Unknown type: '
+               fi
+                       udevadm info --query=name --path=$dev
+        done
+
+        # modules
+        module=$(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*Extra Buttons')
+        module="$module
+        $(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*extra buttons')"
+        module="$module
+        $(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='Sony Vaio Keys')"
+        for m in $module; do
+                for evdev in $m/event*/dev; do
+                        if [ -e "$evdev" ]; then
+                                echo -n 'module: '
+                                udevadm info --query=name --path=${evdev%%/dev}
+                        fi
+                done
+        done
+}
+
+keyboard_devices
diff --git a/lib/udev/keymaps/force-release/common-volume-keys b/src/keymap/force-release-maps/common-volume-keys
similarity index 100%
rename from lib/udev/keymaps/force-release/common-volume-keys
rename to src/keymap/force-release-maps/common-volume-keys
diff --git a/lib/udev/keymaps/force-release/dell-touchpad b/src/keymap/force-release-maps/dell-touchpad
similarity index 100%
rename from lib/udev/keymaps/force-release/dell-touchpad
rename to src/keymap/force-release-maps/dell-touchpad
diff --git a/lib/udev/keymaps/force-release/hp-other b/src/keymap/force-release-maps/hp-other
similarity index 100%
rename from lib/udev/keymaps/force-release/hp-other
rename to src/keymap/force-release-maps/hp-other
diff --git a/src/keymap/force-release-maps/samsung-90x3a b/src/keymap/force-release-maps/samsung-90x3a
new file mode 100644
index 0000000..65707ef
--- /dev/null
+++ b/src/keymap/force-release-maps/samsung-90x3a
@@ -0,0 +1,6 @@
+# list of scancodes (hex or decimal), optional comment
+0xCE # Fn+F8 keyboard backlit up
+0x8D # Fn+F7 keyboard backlit down
+0x97 # Fn+F12 wifi on/off
+0x96 # Fn+F1 performance mode (?)
+0xD5 # Fn+F6 battery life extender
diff --git a/lib/udev/keymaps/force-release/samsung-other b/src/keymap/force-release-maps/samsung-other
similarity index 100%
rename from lib/udev/keymaps/force-release/samsung-other
rename to src/keymap/force-release-maps/samsung-other
diff --git a/src/keymap/keyboard-force-release.sh.in b/src/keymap/keyboard-force-release.sh.in
new file mode 100755
index 0000000..dd040ce
--- /dev/null
+++ b/src/keymap/keyboard-force-release.sh.in
@@ -0,0 +1,22 @@
+#!@rootprefix@/bin/sh -e
+# read list of scancodes, convert hex to decimal and
+# append to the atkbd force_release sysfs attribute
+# $1 sysfs devpath for serioX
+# $2 file with scancode list (hex or dec)
+
+case "$2" in
+        /*) scf="$2" ;;
+        *)  scf="@pkglibexecdir@/keymaps/force-release/$2" ;;
+esac
+
+read attr <"/sys/$1/force_release"
+while read scancode dummy; do
+        case "$scancode" in
+                \#*) ;;
+                *)
+                        scancode=$(($scancode))
+                        attr="$attr${attr:+,}$scancode"
+                        ;;
+        esac
+done <"$scf"
+echo "$attr" >"/sys/$1/force_release"
diff --git a/src/keymap/keymap.c b/src/keymap/keymap.c
new file mode 100644
index 0000000..92ec67b
--- /dev/null
+++ b/src/keymap/keymap.c
@@ -0,0 +1,447 @@
+/*
+ * keymap - dump keymap of an evdev device or set a new keymap from a file
+ *
+ * Based on keyfuzz by Lennart Poettering <mzqrovna@0pointer.net>
+ * Adapted for udev-extras by Martin Pitt <martin.pitt@ubuntu.com>
+ *
+ * Copyright (C) 2006, Lennart Poettering
+ * Copyright (C) 2009, Canonical Ltd.
+ *
+ * keymap is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * keymap is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with keymap; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <linux/limits.h>
+#include <linux/input.h>
+
+const struct key* lookup_key (const char *str, unsigned int len);
+
+#include "keys-from-name.h"
+#include "keys-to-name.h"
+
+#define MAX_SCANCODES 1024
+
+static int evdev_open(const char *dev)
+{
+        int fd;
+        char fn[PATH_MAX];
+
+        if (strncmp(dev, "/dev", 4) != 0) {
+                snprintf(fn, sizeof(fn), "/dev/%s", dev);
+                dev = fn;
+        }
+
+        if ((fd = open(dev, O_RDWR)) < 0) {
+                fprintf(stderr, "error open('%s'): %m\n", dev);
+                return -1;
+        }
+        return fd;
+}
+
+static int evdev_get_keycode(int fd, int scancode, int e)
+{
+        int codes[2];
+
+        codes[0] = scancode;
+        if (ioctl(fd, EVIOCGKEYCODE, codes) < 0) {
+                if (e && errno == EINVAL) {
+                        return -2;
+                } else {
+                        fprintf(stderr, "EVIOCGKEYCODE: %m\n");
+                        return -1;
+                }
+        }
+        return codes[1];
+}
+
+static int evdev_set_keycode(int fd, int scancode, int keycode)
+{
+        int codes[2];
+
+        codes[0] = scancode;
+        codes[1] = keycode;
+
+        if (ioctl(fd, EVIOCSKEYCODE, codes) < 0) {
+                fprintf(stderr, "EVIOCSKEYCODE: %m\n");
+                return -1;
+        }
+        return 0;
+}
+
+static int evdev_driver_version(int fd, char *v, size_t l)
+{
+        int version;
+
+        if (ioctl(fd, EVIOCGVERSION, &version)) {
+                fprintf(stderr, "EVIOCGVERSION: %m\n");
+                return -1;
+        }
+
+        snprintf(v, l, "%i.%i.%i.", version >> 16, (version >> 8) & 0xff, version & 0xff);
+        return 0;
+}
+
+static int evdev_device_name(int fd, char *n, size_t l)
+{
+        if (ioctl(fd, EVIOCGNAME(l), n) < 0) {
+                fprintf(stderr, "EVIOCGNAME: %m\n");
+                return -1;
+        }
+        return 0;
+}
+
+/* Return a lower-case string with KEY_ prefix removed */
+static const char* format_keyname(const char* key) {
+        static char result[101];
+        const char* s;
+        int len;
+
+        for (s = key+4, len = 0; *s && len < 100; ++len, ++s)
+                result[len] = tolower(*s);
+        result[len] = '\0';
+        return result;
+}
+
+static int dump_table(int fd) {
+        char version[256], name[256];
+        int scancode, r = -1;
+
+        if (evdev_driver_version(fd, version, sizeof(version)) < 0)
+                goto fail;
+
+        if (evdev_device_name(fd, name, sizeof(name)) < 0)
+                goto fail;
+
+        printf("### evdev %s, driver '%s'\n", version, name);
+
+        r = 0;
+        for (scancode = 0; scancode < MAX_SCANCODES; scancode++) {
+                int keycode;
+
+                if ((keycode = evdev_get_keycode(fd, scancode, 1)) < 0) {
+                        if (keycode == -2)
+                                continue;
+                        r = -1;
+                        break;
+                }
+
+                if (keycode < KEY_MAX && key_names[keycode])
+                        printf("0x%03x %s\n", scancode, format_keyname(key_names[keycode]));
+                else
+                        printf("0x%03x 0x%03x\n", scancode, keycode);
+        }
+fail:
+        return r;
+}
+
+static void set_key(int fd, const char* scancode_str, const char* keyname)
+{
+        unsigned scancode;
+        char *endptr;
+        char t[105] = "KEY_UNKNOWN";
+        const struct key *k;
+
+        scancode = (unsigned) strtol(scancode_str, &endptr, 0);
+        if (*endptr != '\0') {
+                fprintf(stderr, "ERROR: Invalid scancode\n");
+                exit(1);
+        }
+
+        snprintf(t, sizeof(t), "KEY_%s", keyname);
+
+        if (!(k = lookup_key(t, strlen(t)))) {
+                fprintf(stderr, "ERROR: Unknown key name '%s'\n", keyname);
+                exit(1);
+        }
+
+        if (evdev_set_keycode(fd, scancode, k->id) < 0)
+                fprintf(stderr, "setting scancode 0x%2X to key code %i failed\n",
+                        scancode, k->id);
+        else
+                printf("setting scancode 0x%2X to key code %i\n",
+                        scancode, k->id);
+}
+
+static int merge_table(int fd, FILE *f) {
+        int r = 0;
+        int line = 0;
+
+        while (!feof(f)) {
+                char s[256], *p;
+                int scancode, new_keycode, old_keycode;
+
+                if (!fgets(s, sizeof(s), f))
+                        break;
+
+                line++;
+                p = s+strspn(s, "\t ");
+                if (*p == '#' || *p == '\n')
+                        continue;
+
+                if (sscanf(p, "%i %i", &scancode, &new_keycode) != 2) {
+                        char t[105] = "KEY_UNKNOWN";
+                        const struct key *k;
+
+                        if (sscanf(p, "%i %100s", &scancode, t+4) != 2) {
+                                fprintf(stderr, "WARNING: Parse failure at line %i, ignoring.\n", line);
+                                r = -1;
+                                continue;
+                        }
+
+                        if (!(k = lookup_key(t, strlen(t)))) {
+                                fprintf(stderr, "WARNING: Unknown key '%s' at line %i, ignoring.\n", t, line);
+                                r = -1;
+                                continue;
+                        }
+
+                        new_keycode = k->id;
+                }
+
+
+                if ((old_keycode = evdev_get_keycode(fd, scancode, 0)) < 0) {
+                        r = -1;
+                        goto fail;
+                }
+
+                if (evdev_set_keycode(fd, scancode, new_keycode) < 0) {
+                        r = -1;
+                        goto fail;
+                }
+
+                if (new_keycode != old_keycode)
+                        fprintf(stderr, "Remapped scancode 0x%02x to 0x%02x (prior: 0x%02x)\n",
+                                scancode, new_keycode, old_keycode);
+        }
+fail:
+        fclose(f);
+        return r;
+}
+
+
+/* read one event; return 1 if valid */
+static int read_event(int fd, struct input_event* ev)
+{
+        int ret;
+        ret = read(fd, ev, sizeof(struct input_event));
+
+        if (ret < 0) {
+                perror("read");
+                return 0;
+        }
+        if (ret != sizeof(struct input_event)) {
+                fprintf(stderr, "did not get enough data for event struct, aborting\n");
+                return 0;
+        }
+
+        return 1;
+}
+
+static void print_key(uint32_t scancode, uint16_t keycode, int has_scan, int has_key)
+{
+        const char *keyname;
+
+        /* ignore key release events */
+        if (has_key == 1)
+                return;
+
+        if (has_key == 0 && has_scan != 0) {
+                fprintf(stderr, "got scan code event 0x%02X without a key code event\n",
+                        scancode);
+                return;
+        }
+
+        if (has_scan != 0)
+                printf("scan code: 0x%02X   ", scancode);
+        else
+                printf("(no scan code received)  ");
+
+        keyname = key_names[keycode];
+        if (keyname != NULL)
+                printf("key code: %s\n", format_keyname(keyname));
+        else
+                printf("key code: %03X\n", keycode);
+}
+
+static void interactive(int fd)
+{
+        struct input_event ev;
+        uint32_t last_scan = 0;
+        uint16_t last_key = 0;
+        int has_scan; /* boolean */
+        int has_key; /* 0: none, 1: release, 2: press */
+
+        /* grab input device */
+        ioctl(fd, EVIOCGRAB, 1);
+        puts("Press ESC to finish, or Control-C if this device is not your primary keyboard");
+
+        has_scan = has_key = 0;
+        while (read_event(fd, &ev)) {
+                /* Drivers usually send the scan code first, then the key code,
+                 * then a SYN. Some drivers (like thinkpad_acpi) send the key
+                 * code first, and some drivers might not send SYN events, so
+                 * keep a robust state machine which can deal with any of those
+                 */
+
+                if (ev.type == EV_MSC && ev.code == MSC_SCAN) {
+                        if (has_scan) {
+                                fputs("driver did not send SYN event in between key events; previous event:\n",
+                                      stderr);
+                                print_key(last_scan, last_key, has_scan, has_key);
+                                has_key = 0;
+                        }
+
+                        last_scan = ev.value;
+                        has_scan = 1;
+                        /*printf("--- got scan %u; has scan %i key %i\n", last_scan, has_scan, has_key); */
+                }
+                else if (ev.type == EV_KEY) {
+                        if (has_key) {
+                                fputs("driver did not send SYN event in between key events; previous event:\n",
+                                      stderr);
+                                print_key(last_scan, last_key, has_scan, has_key);
+                                has_scan = 0;
+                        }
+
+                        last_key = ev.code;
+                        has_key = 1 + ev.value;
+                        /*printf("--- got key %hu; has scan %i key %i\n", last_key, has_scan, has_key);*/
+
+                        /* Stop on ESC */
+                        if (ev.code == KEY_ESC && ev.value == 0)
+                                break;
+                }
+                else if (ev.type == EV_SYN) {
+                        /*printf("--- got SYN; has scan %i key %i\n", has_scan, has_key);*/
+                        print_key(last_scan, last_key, has_scan, has_key);
+
+                        has_scan = has_key = 0;
+                }
+
+        }
+
+        /* release input device */
+        ioctl(fd, EVIOCGRAB, 0);
+}
+
+static void help(int error)
+{
+        const char* h = "Usage: keymap <event device> [<map file>]\n"
+                        "       keymap <event device> scancode keyname [...]\n"
+                        "       keymap -i <event device>\n";
+        if (error) {
+                fputs(h, stderr);
+                exit(2);
+        } else {
+                fputs(h, stdout);
+                exit(0);
+        }
+}
+
+int main(int argc, char **argv)
+{
+        static const struct option options[] = {
+                { "help", no_argument, NULL, 'h' },
+                { "interactive", no_argument, NULL, 'i' },
+                {}
+        };
+        int fd = -1;
+        int opt_interactive = 0;
+        int i;
+
+        while (1) {
+                int option;
+
+                option = getopt_long(argc, argv, "hi", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'h':
+                        help(0);
+
+                case 'i':
+                        opt_interactive = 1;
+                        break;
+                default:
+                        return 1;
+                }
+        }
+
+        if (argc < optind+1)
+                help (1);
+
+        if ((fd = evdev_open(argv[optind])) < 0)
+                return 3;
+
+        /* one argument (device): dump or interactive */
+        if (argc == optind+1) {
+                if (opt_interactive)
+                        interactive(fd);
+                else
+                        dump_table(fd);
+                return 0;
+        }
+
+        /* two arguments (device, mapfile): set map file */
+        if (argc == optind+2) {
+                const char *filearg = argv[optind+1];
+                if (strchr(filearg, '/')) {
+                        /* Keymap file argument is a path */
+                        FILE *f = fopen(filearg, "r");
+                        if (f)
+                                merge_table(fd, f);
+                        else
+                                perror(filearg);
+                } else {
+                        /* Keymap file argument is a filename */
+                        /* Open override file if present, otherwise default file */
+                        char keymap_path[PATH_MAX];
+                        snprintf(keymap_path, sizeof(keymap_path), "%s%s", SYSCONFDIR "/udev/keymaps/", filearg);
+                        FILE *f = fopen(keymap_path, "r");
+                        if (f) {
+                                merge_table(fd, f);
+                        } else {
+                                snprintf(keymap_path, sizeof(keymap_path), "%s%s", PKGLIBEXECDIR "/keymaps/", filearg);
+                                f = fopen(keymap_path, "r");
+                                if (f)
+                                        merge_table(fd, f);
+                                else
+                                        perror(keymap_path);
+                        }
+                }
+                return 0;
+        }
+
+        /* more arguments (device, scancode/keyname pairs): set keys directly */
+        if ((argc - optind - 1) % 2 == 0) {
+                for (i = optind+1; i < argc; i += 2)
+                        set_key(fd, argv[i], argv[i+1]);
+                return 0;
+        }
+
+        /* invalid number of arguments */
+        help(1);
+        return 1; /* not reached */
+}
diff --git a/lib/udev/keymaps/acer b/src/keymap/keymaps/acer
similarity index 100%
rename from lib/udev/keymaps/acer
rename to src/keymap/keymaps/acer
diff --git a/lib/udev/keymaps/acer-aspire_5720 b/src/keymap/keymaps/acer-aspire_5720
similarity index 81%
rename from lib/udev/keymaps/acer-aspire_5720
rename to src/keymap/keymaps/acer-aspire_5720
index b3515b7..1496d63 100644
--- a/lib/udev/keymaps/acer-aspire_5720
+++ b/src/keymap/keymaps/acer-aspire_5720
@@ -1,5 +1,4 @@
 0x84 bluetooth  # sent when bluetooth module missing, and key pressed
-0x92 media	# acer arcade
+0x92 media      # acer arcade
 0xD4 bluetooth  # bluetooth on
 0xD9 bluetooth  # bluetooth off
-
diff --git a/lib/udev/keymaps/acer-aspire_5920g b/src/keymap/keymaps/acer-aspire_5920g
similarity index 100%
rename from lib/udev/keymaps/acer-aspire_5920g
rename to src/keymap/keymaps/acer-aspire_5920g
diff --git a/lib/udev/keymaps/acer-aspire_6920 b/src/keymap/keymaps/acer-aspire_6920
similarity index 100%
rename from lib/udev/keymaps/acer-aspire_6920
rename to src/keymap/keymaps/acer-aspire_6920
diff --git a/lib/udev/keymaps/acer-aspire_8930 b/src/keymap/keymaps/acer-aspire_8930
similarity index 98%
rename from lib/udev/keymaps/acer-aspire_8930
rename to src/keymap/keymaps/acer-aspire_8930
index ffe9f37..fb27bfb 100644
--- a/lib/udev/keymaps/acer-aspire_8930
+++ b/src/keymap/keymaps/acer-aspire_8930
@@ -3,4 +3,3 @@
 0x89 fastforward
 0x92 media        # key 'ARCADE' on cine dash media console
 0x9E back
-
diff --git a/lib/udev/keymaps/acer-travelmate_c300 b/src/keymap/keymaps/acer-travelmate_c300
similarity index 100%
rename from lib/udev/keymaps/acer-travelmate_c300
rename to src/keymap/keymaps/acer-travelmate_c300
diff --git a/lib/udev/keymaps/asus b/src/keymap/keymaps/asus
similarity index 100%
rename from lib/udev/keymaps/asus
rename to src/keymap/keymaps/asus
diff --git a/lib/udev/keymaps/compaq-e_evo b/src/keymap/keymaps/compaq-e_evo
similarity index 100%
rename from lib/udev/keymaps/compaq-e_evo
rename to src/keymap/keymaps/compaq-e_evo
diff --git a/lib/udev/keymaps/dell b/src/keymap/keymaps/dell
similarity index 100%
rename from lib/udev/keymaps/dell
rename to src/keymap/keymaps/dell
diff --git a/lib/udev/keymaps/dell-latitude-xt2 b/src/keymap/keymaps/dell-latitude-xt2
similarity index 100%
rename from lib/udev/keymaps/dell-latitude-xt2
rename to src/keymap/keymaps/dell-latitude-xt2
diff --git a/lib/udev/keymaps/everex-xt5000 b/src/keymap/keymaps/everex-xt5000
similarity index 100%
rename from lib/udev/keymaps/everex-xt5000
rename to src/keymap/keymaps/everex-xt5000
diff --git a/src/keymap/keymaps/fujitsu-amilo_li_2732 b/src/keymap/keymaps/fujitsu-amilo_li_2732
new file mode 100644
index 0000000..9b8b36a
--- /dev/null
+++ b/src/keymap/keymaps/fujitsu-amilo_li_2732
@@ -0,0 +1,3 @@
+0xD9 brightnessdown # Fn+F8 brightness down
+0xEF brightnessup # Fn+F9 brightness up
+0xA9 switchvideomode # Fn+F10 Cycle between available video outputs
diff --git a/lib/udev/keymaps/fujitsu-amilo_pa_2548 b/src/keymap/keymaps/fujitsu-amilo_pa_2548
similarity index 100%
rename from lib/udev/keymaps/fujitsu-amilo_pa_2548
rename to src/keymap/keymaps/fujitsu-amilo_pa_2548
diff --git a/lib/udev/keymaps/fujitsu-amilo_pro_edition_v3505 b/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505
similarity index 100%
rename from lib/udev/keymaps/fujitsu-amilo_pro_edition_v3505
rename to src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505
diff --git a/lib/udev/keymaps/fujitsu-amilo_pro_v3205 b/src/keymap/keymaps/fujitsu-amilo_pro_v3205
similarity index 100%
rename from lib/udev/keymaps/fujitsu-amilo_pro_v3205
rename to src/keymap/keymaps/fujitsu-amilo_pro_v3205
diff --git a/lib/udev/keymaps/fujitsu-amilo_si_1520 b/src/keymap/keymaps/fujitsu-amilo_si_1520
similarity index 100%
rename from lib/udev/keymaps/fujitsu-amilo_si_1520
rename to src/keymap/keymaps/fujitsu-amilo_si_1520
diff --git a/lib/udev/keymaps/fujitsu-esprimo_mobile_v5 b/src/keymap/keymaps/fujitsu-esprimo_mobile_v5
similarity index 100%
rename from lib/udev/keymaps/fujitsu-esprimo_mobile_v5
rename to src/keymap/keymaps/fujitsu-esprimo_mobile_v5
diff --git a/lib/udev/keymaps/fujitsu-esprimo_mobile_v6 b/src/keymap/keymaps/fujitsu-esprimo_mobile_v6
similarity index 100%
rename from lib/udev/keymaps/fujitsu-esprimo_mobile_v6
rename to src/keymap/keymaps/fujitsu-esprimo_mobile_v6
diff --git a/lib/udev/keymaps/genius-slimstar-320 b/src/keymap/keymaps/genius-slimstar-320
similarity index 100%
rename from lib/udev/keymaps/genius-slimstar-320
rename to src/keymap/keymaps/genius-slimstar-320
diff --git a/lib/udev/keymaps/hewlett-packard b/src/keymap/keymaps/hewlett-packard
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard
rename to src/keymap/keymaps/hewlett-packard
diff --git a/lib/udev/keymaps/hewlett-packard-2510p_2530p b/src/keymap/keymaps/hewlett-packard-2510p_2530p
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard-2510p_2530p
rename to src/keymap/keymaps/hewlett-packard-2510p_2530p
diff --git a/lib/udev/keymaps/hewlett-packard-compaq_elitebook b/src/keymap/keymaps/hewlett-packard-compaq_elitebook
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard-compaq_elitebook
rename to src/keymap/keymaps/hewlett-packard-compaq_elitebook
diff --git a/lib/udev/keymaps/hewlett-packard-pavilion b/src/keymap/keymaps/hewlett-packard-pavilion
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard-pavilion
rename to src/keymap/keymaps/hewlett-packard-pavilion
diff --git a/lib/udev/keymaps/hewlett-packard-presario-2100 b/src/keymap/keymaps/hewlett-packard-presario-2100
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard-presario-2100
rename to src/keymap/keymaps/hewlett-packard-presario-2100
diff --git a/lib/udev/keymaps/hewlett-packard-tablet b/src/keymap/keymaps/hewlett-packard-tablet
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard-tablet
rename to src/keymap/keymaps/hewlett-packard-tablet
diff --git a/lib/udev/keymaps/hewlett-packard-tx2 b/src/keymap/keymaps/hewlett-packard-tx2
similarity index 100%
rename from lib/udev/keymaps/hewlett-packard-tx2
rename to src/keymap/keymaps/hewlett-packard-tx2
diff --git a/lib/udev/keymaps/ibm-thinkpad-usb-keyboard-trackpoint b/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint
similarity index 100%
rename from lib/udev/keymaps/ibm-thinkpad-usb-keyboard-trackpoint
rename to src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint
diff --git a/lib/udev/keymaps/inventec-symphony_6.0_7.0 b/src/keymap/keymaps/inventec-symphony_6.0_7.0
similarity index 100%
rename from lib/udev/keymaps/inventec-symphony_6.0_7.0
rename to src/keymap/keymaps/inventec-symphony_6.0_7.0
diff --git a/lib/udev/keymaps/lenovo-3000 b/src/keymap/keymaps/lenovo-3000
similarity index 100%
rename from lib/udev/keymaps/lenovo-3000
rename to src/keymap/keymaps/lenovo-3000
diff --git a/src/keymap/keymaps/lenovo-ideapad b/src/keymap/keymaps/lenovo-ideapad
new file mode 100644
index 0000000..fc33983
--- /dev/null
+++ b/src/keymap/keymaps/lenovo-ideapad
@@ -0,0 +1,8 @@
+# Key codes observed on S10-3, assumed valid on other IdeaPad models
+0x81 rfkill             # does nothing in BIOS
+0x83 display_off        # BIOS toggles screen state
+0xB9 brightnessup       # does nothing in BIOS
+0xBA brightnessdown     # does nothing in BIOS
+0xF1 camera             # BIOS toggles camera power
+0xf2 f21                # touchpad toggle (key alternately emits f2 and f3)
+0xf3 f21
diff --git a/lib/udev/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint b/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
similarity index 88%
rename from lib/udev/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
rename to src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
index 3e94547..47e8846 100644
--- a/lib/udev/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
+++ b/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
@@ -10,4 +10,4 @@
 0x90011 prog1 # Thinkvantage button
 
 0x90015 camera # Fn+F6 headset/camera VoIP key  ??
-0x90010 micmute # Microphone mute button 
+0x90010 micmute # Microphone mute button
diff --git a/lib/udev/keymaps/lenovo-thinkpad_x200_tablet b/src/keymap/keymaps/lenovo-thinkpad_x200_tablet
similarity index 100%
rename from lib/udev/keymaps/lenovo-thinkpad_x200_tablet
rename to src/keymap/keymaps/lenovo-thinkpad_x200_tablet
diff --git a/lib/udev/keymaps/lenovo-thinkpad_x6_tablet b/src/keymap/keymaps/lenovo-thinkpad_x6_tablet
similarity index 100%
rename from lib/udev/keymaps/lenovo-thinkpad_x6_tablet
rename to src/keymap/keymaps/lenovo-thinkpad_x6_tablet
diff --git a/lib/udev/keymaps/lg-x110 b/src/keymap/keymaps/lg-x110
similarity index 100%
rename from lib/udev/keymaps/lg-x110
rename to src/keymap/keymaps/lg-x110
diff --git a/lib/udev/keymaps/logitech-wave b/src/keymap/keymaps/logitech-wave
similarity index 100%
rename from lib/udev/keymaps/logitech-wave
rename to src/keymap/keymaps/logitech-wave
diff --git a/lib/udev/keymaps/logitech-wave-cordless b/src/keymap/keymaps/logitech-wave-cordless
similarity index 100%
rename from lib/udev/keymaps/logitech-wave-cordless
rename to src/keymap/keymaps/logitech-wave-cordless
diff --git a/lib/udev/keymaps/logitech-wave-pro-cordless b/src/keymap/keymaps/logitech-wave-pro-cordless
similarity index 100%
rename from lib/udev/keymaps/logitech-wave-pro-cordless
rename to src/keymap/keymaps/logitech-wave-pro-cordless
diff --git a/lib/udev/keymaps/maxdata-pro_7000 b/src/keymap/keymaps/maxdata-pro_7000
similarity index 100%
rename from lib/udev/keymaps/maxdata-pro_7000
rename to src/keymap/keymaps/maxdata-pro_7000
diff --git a/lib/udev/keymaps/medion-fid2060 b/src/keymap/keymaps/medion-fid2060
similarity index 100%
rename from lib/udev/keymaps/medion-fid2060
rename to src/keymap/keymaps/medion-fid2060
diff --git a/lib/udev/keymaps/medionnb-a555 b/src/keymap/keymaps/medionnb-a555
similarity index 100%
rename from lib/udev/keymaps/medionnb-a555
rename to src/keymap/keymaps/medionnb-a555
diff --git a/lib/udev/keymaps/micro-star b/src/keymap/keymaps/micro-star
similarity index 100%
rename from lib/udev/keymaps/micro-star
rename to src/keymap/keymaps/micro-star
diff --git a/lib/udev/keymaps/module-asus-w3j b/src/keymap/keymaps/module-asus-w3j
similarity index 100%
rename from lib/udev/keymaps/module-asus-w3j
rename to src/keymap/keymaps/module-asus-w3j
diff --git a/lib/udev/keymaps/module-ibm b/src/keymap/keymaps/module-ibm
similarity index 100%
rename from lib/udev/keymaps/module-ibm
rename to src/keymap/keymaps/module-ibm
diff --git a/lib/udev/keymaps/module-lenovo b/src/keymap/keymaps/module-lenovo
similarity index 100%
rename from lib/udev/keymaps/module-lenovo
rename to src/keymap/keymaps/module-lenovo
diff --git a/lib/udev/keymaps/module-sony b/src/keymap/keymaps/module-sony
similarity index 100%
rename from lib/udev/keymaps/module-sony
rename to src/keymap/keymaps/module-sony
diff --git a/lib/udev/keymaps/module-sony-old b/src/keymap/keymaps/module-sony-old
similarity index 100%
rename from lib/udev/keymaps/module-sony-old
rename to src/keymap/keymaps/module-sony-old
diff --git a/lib/udev/keymaps/module-sony-vgn b/src/keymap/keymaps/module-sony-vgn
similarity index 100%
rename from lib/udev/keymaps/module-sony-vgn
rename to src/keymap/keymaps/module-sony-vgn
diff --git a/lib/udev/keymaps/olpc-xo b/src/keymap/keymaps/olpc-xo
similarity index 100%
rename from lib/udev/keymaps/olpc-xo
rename to src/keymap/keymaps/olpc-xo
diff --git a/lib/udev/keymaps/onkyo b/src/keymap/keymaps/onkyo
similarity index 100%
rename from lib/udev/keymaps/onkyo
rename to src/keymap/keymaps/onkyo
diff --git a/lib/udev/keymaps/oqo-model2 b/src/keymap/keymaps/oqo-model2
similarity index 100%
rename from lib/udev/keymaps/oqo-model2
rename to src/keymap/keymaps/oqo-model2
diff --git a/src/keymap/keymaps/samsung-90x3a b/src/keymap/keymaps/samsung-90x3a
new file mode 100644
index 0000000..8b65eb6
--- /dev/null
+++ b/src/keymap/keymaps/samsung-90x3a
@@ -0,0 +1,5 @@
+0x96 kbdillumup         # Fn+F8 keyboard backlit up
+0x97 kbdillumdown       # Fn+F7 keyboard backlit down
+0xD5 wlan               # Fn+F12 wifi on/off
+0xCE prog1              # Fn+F1 performance mode
+0x8D prog2              # Fn+F6 battery life extender
diff --git a/lib/udev/keymaps/samsung-other b/src/keymap/keymaps/samsung-other
similarity index 100%
rename from lib/udev/keymaps/samsung-other
rename to src/keymap/keymaps/samsung-other
diff --git a/lib/udev/keymaps/samsung-sq1us b/src/keymap/keymaps/samsung-sq1us
similarity index 83%
rename from lib/udev/keymaps/samsung-sq1us
rename to src/keymap/keymaps/samsung-sq1us
index 3e05199..ea2141e 100644
--- a/lib/udev/keymaps/samsung-sq1us
+++ b/src/keymap/keymaps/samsung-sq1us
@@ -1,6 +1,6 @@
 0xD4 menu
 0xD8 f1
-0xD9 f10 
+0xD9 f10
 0xD6 f3
 0xD7 f9
 0xE4 f5
diff --git a/lib/udev/keymaps/samsung-sx20s b/src/keymap/keymaps/samsung-sx20s
similarity index 100%
rename from lib/udev/keymaps/samsung-sx20s
rename to src/keymap/keymaps/samsung-sx20s
diff --git a/lib/udev/keymaps/toshiba-satellite_a100 b/src/keymap/keymaps/toshiba-satellite_a100
similarity index 100%
rename from lib/udev/keymaps/toshiba-satellite_a100
rename to src/keymap/keymaps/toshiba-satellite_a100
diff --git a/lib/udev/keymaps/toshiba-satellite_a110 b/src/keymap/keymaps/toshiba-satellite_a110
similarity index 100%
rename from lib/udev/keymaps/toshiba-satellite_a110
rename to src/keymap/keymaps/toshiba-satellite_a110
diff --git a/lib/udev/keymaps/toshiba-satellite_m30x b/src/keymap/keymaps/toshiba-satellite_m30x
similarity index 98%
rename from lib/udev/keymaps/toshiba-satellite_m30x
rename to src/keymap/keymaps/toshiba-satellite_m30x
index 9280ae0..ae8e349 100644
--- a/lib/udev/keymaps/toshiba-satellite_m30x
+++ b/src/keymap/keymaps/toshiba-satellite_m30x
@@ -4,4 +4,3 @@
 0x93 media
 0x9e f22 #touchpad_enable
 0x9f f23 #touchpad_disable
-
diff --git a/lib/udev/keymaps/zepto-znote b/src/keymap/keymaps/zepto-znote
similarity index 100%
rename from lib/udev/keymaps/zepto-znote
rename to src/keymap/keymaps/zepto-znote
diff --git a/src/libudev-device-private.c b/src/libudev-device-private.c
new file mode 100644
index 0000000..13fdb8e
--- /dev/null
+++ b/src/libudev-device-private.c
@@ -0,0 +1,185 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static void udev_device_tag(struct udev_device *dev, const char *tag, bool add)
+{
+        const char *id;
+        struct udev *udev = udev_device_get_udev(dev);
+        char filename[UTIL_PATH_SIZE];
+
+        id = udev_device_get_id_filename(dev);
+        if (id == NULL)
+                return;
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/tags/", tag, "/", id, NULL);
+
+        if (add) {
+                int fd;
+
+                util_create_path(udev, filename);
+                fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
+                if (fd >= 0)
+                        close(fd);
+        } else {
+                unlink(filename);
+        }
+}
+
+int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add)
+{
+        struct udev_list_entry *list_entry;
+        bool found;
+
+        if (add && dev_old != NULL) {
+                /* delete possible left-over tags */
+                udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(dev_old)) {
+                        const char *tag_old = udev_list_entry_get_name(list_entry);
+                        struct udev_list_entry *list_entry_current;
+
+                        found = false;
+                        udev_list_entry_foreach(list_entry_current, udev_device_get_tags_list_entry(dev)) {
+                                const char *tag = udev_list_entry_get_name(list_entry_current);
+
+                                if (strcmp(tag, tag_old) == 0) {
+                                        found = true;
+                                        break;
+                                }
+                        }
+                        if (!found)
+                                udev_device_tag(dev_old, tag_old, false);
+                }
+        }
+
+        udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(dev))
+                udev_device_tag(dev, udev_list_entry_get_name(list_entry), add);
+
+        return 0;
+}
+
+static bool device_has_info(struct udev_device *udev_device)
+{
+        struct udev_list_entry *list_entry;
+
+        if (udev_device_get_devlinks_list_entry(udev_device) != NULL)
+                return true;
+        if (udev_device_get_devlink_priority(udev_device) != 0)
+                return true;
+        udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device))
+                if (udev_list_entry_get_num(list_entry))
+                        return true;
+        if (udev_device_get_tags_list_entry(udev_device) != NULL)
+                return true;
+        if (udev_device_get_watch_handle(udev_device) >= 0)
+                return true;
+        return false;
+}
+
+int udev_device_update_db(struct udev_device *udev_device)
+{
+        bool has_info;
+        const char *id;
+        struct udev *udev = udev_device_get_udev(udev_device);
+        char filename[UTIL_PATH_SIZE];
+        char filename_tmp[UTIL_PATH_SIZE];
+        FILE *f;
+
+        id = udev_device_get_id_filename(udev_device);
+        if (id == NULL)
+                return -1;
+
+        has_info = device_has_info(udev_device);
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/data/", id, NULL);
+
+        /* do not store anything for otherwise empty devices */
+        if (!has_info &&
+            major(udev_device_get_devnum(udev_device)) == 0 &&
+            udev_device_get_ifindex(udev_device) == 0) {
+                unlink(filename);
+                return 0;
+        }
+
+        /* write a database file */
+        util_strscpyl(filename_tmp, sizeof(filename_tmp), filename, ".tmp", NULL);
+        util_create_path(udev, filename_tmp);
+        f = fopen(filename_tmp, "we");
+        if (f == NULL) {
+                err(udev, "unable to create temporary db file '%s': %m\n", filename_tmp);
+                return -1;
+        }
+
+        /*
+         * set 'sticky' bit to indicate that we should not clean the
+         * database when we transition from initramfs to the real root
+         */
+        if (udev_device_get_db_persist(udev_device))
+                fchmod(fileno(f), 01644);
+
+        if (has_info) {
+                struct udev_list_entry *list_entry;
+
+                if (major(udev_device_get_devnum(udev_device)) > 0) {
+                        size_t devlen = strlen(udev_get_dev_path(udev))+1;
+
+                        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(udev_device))
+                                fprintf(f, "S:%s\n", &udev_list_entry_get_name(list_entry)[devlen]);
+                        if (udev_device_get_devlink_priority(udev_device) != 0)
+                                fprintf(f, "L:%i\n", udev_device_get_devlink_priority(udev_device));
+                        if (udev_device_get_watch_handle(udev_device) >= 0)
+                                fprintf(f, "W:%i\n", udev_device_get_watch_handle(udev_device));
+                }
+
+                if (udev_device_get_usec_initialized(udev_device) > 0)
+                        fprintf(f, "I:%llu\n", udev_device_get_usec_initialized(udev_device));
+
+                udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device)) {
+                        if (!udev_list_entry_get_num(list_entry))
+                                continue;
+                        fprintf(f, "E:%s=%s\n",
+                                udev_list_entry_get_name(list_entry),
+                                udev_list_entry_get_value(list_entry));
+                }
+
+                udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device))
+                        fprintf(f, "G:%s\n", udev_list_entry_get_name(list_entry));
+        }
+
+        fclose(f);
+        rename(filename_tmp, filename);
+        info(udev, "created %s file '%s' for '%s'\n", has_info ? "db" : "empty",
+             filename, udev_device_get_devpath(udev_device));
+        return 0;
+}
+
+int udev_device_delete_db(struct udev_device *udev_device)
+{
+        const char *id;
+        struct udev *udev = udev_device_get_udev(udev_device);
+        char filename[UTIL_PATH_SIZE];
+
+        id = udev_device_get_id_filename(udev_device);
+        if (id == NULL)
+                return -1;
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/data/", id, NULL);
+        unlink(filename);
+        return 0;
+}
diff --git a/src/libudev-device.c b/src/libudev-device.c
new file mode 100644
index 0000000..10f28b8
--- /dev/null
+++ b/src/libudev-device.c
@@ -0,0 +1,1744 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <net/if.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <linux/sockios.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-device
+ * @short_description: kernel sys devices
+ *
+ * Representation of kernel sys devices. Devices are uniquely identified
+ * by their syspath, every device has exactly one path in the kernel sys
+ * filesystem. Devices usually belong to a kernel subsystem, and and have
+ * a unique name inside that subsystem.
+ */
+
+/**
+ * udev_device:
+ *
+ * Opaque object representing one kernel sys device.
+ */
+struct udev_device {
+        struct udev *udev;
+        struct udev_device *parent_device;
+        char *syspath;
+        const char *devpath;
+        char *sysname;
+        const char *sysnum;
+        char *devnode;
+        mode_t devnode_mode;
+        char *subsystem;
+        char *devtype;
+        char *driver;
+        char *action;
+        char *devpath_old;
+        char *id_filename;
+        char **envp;
+        char *monitor_buf;
+        size_t monitor_buf_len;
+        struct udev_list devlinks_list;
+        struct udev_list properties_list;
+        struct udev_list sysattr_value_list;
+        struct udev_list sysattr_list;
+        struct udev_list tags_list;
+        unsigned long long int seqnum;
+        unsigned long long int usec_initialized;
+        int devlink_priority;
+        int refcount;
+        dev_t devnum;
+        int ifindex;
+        int watch_handle;
+        int maj, min;
+        bool parent_set;
+        bool subsystem_set;
+        bool devtype_set;
+        bool devlinks_uptodate;
+        bool envp_uptodate;
+        bool tags_uptodate;
+        bool driver_set;
+        bool info_loaded;
+        bool db_loaded;
+        bool uevent_loaded;
+        bool is_initialized;
+        bool sysattr_list_read;
+        bool db_persist;
+};
+
+/**
+ * udev_device_get_seqnum:
+ * @udev_device: udev device
+ *
+ * This is only valid if the device was received through a monitor. Devices read from
+ * sys do not have a sequence number.
+ *
+ * Returns: the kernel event sequence number, or 0 if there is no sequence number available.
+ **/
+UDEV_EXPORT unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return 0;
+        return udev_device->seqnum;
+}
+
+static int udev_device_set_seqnum(struct udev_device *udev_device, unsigned long long int seqnum)
+{
+        char num[32];
+
+        udev_device->seqnum = seqnum;
+        snprintf(num, sizeof(num), "%llu", seqnum);
+        udev_device_add_property(udev_device, "SEQNUM", num);
+        return 0;
+}
+
+int udev_device_get_ifindex(struct udev_device *udev_device)
+{
+        if (!udev_device->info_loaded)
+                udev_device_read_uevent_file(udev_device);
+        return udev_device->ifindex;
+}
+
+static int udev_device_set_ifindex(struct udev_device *udev_device, int ifindex)
+{
+        char num[32];
+
+        udev_device->ifindex = ifindex;
+        snprintf(num, sizeof(num), "%u", ifindex);
+        udev_device_add_property(udev_device, "IFINDEX", num);
+        return 0;
+}
+
+/**
+ * udev_device_get_devnum:
+ * @udev_device: udev device
+ *
+ * Returns: the device major/minor number.
+ **/
+UDEV_EXPORT dev_t udev_device_get_devnum(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return makedev(0, 0);
+        if (!udev_device->info_loaded)
+                udev_device_read_uevent_file(udev_device);
+        return udev_device->devnum;
+}
+
+static int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum)
+{
+        char num[32];
+
+        udev_device->devnum = devnum;
+
+        snprintf(num, sizeof(num), "%u", major(devnum));
+        udev_device_add_property(udev_device, "MAJOR", num);
+        snprintf(num, sizeof(num), "%u", minor(devnum));
+        udev_device_add_property(udev_device, "MINOR", num);
+        return 0;
+}
+
+const char *udev_device_get_devpath_old(struct udev_device *udev_device)
+{
+        return udev_device->devpath_old;
+}
+
+static int udev_device_set_devpath_old(struct udev_device *udev_device, const char *devpath_old)
+{
+        const char *pos;
+
+        free(udev_device->devpath_old);
+        udev_device->devpath_old = strdup(devpath_old);
+        if (udev_device->devpath_old == NULL)
+                return -ENOMEM;
+        udev_device_add_property(udev_device, "DEVPATH_OLD", udev_device->devpath_old);
+
+        pos = strrchr(udev_device->devpath_old, '/');
+        if (pos == NULL)
+                return -EINVAL;
+        return 0;
+}
+
+/**
+ * udev_device_get_driver:
+ * @udev_device: udev device
+ *
+ * Returns: the driver string, or #NULL if there is no driver attached.
+ **/
+UDEV_EXPORT const char *udev_device_get_driver(struct udev_device *udev_device)
+{
+        char driver[UTIL_NAME_SIZE];
+
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->driver_set) {
+                udev_device->driver_set = true;
+                if (util_get_sys_core_link_value(udev_device->udev, "driver", udev_device->syspath, driver, sizeof(driver)) > 0)
+                        udev_device->driver = strdup(driver);
+        }
+        return udev_device->driver;
+}
+
+static int udev_device_set_driver(struct udev_device *udev_device, const char *driver)
+{
+        free(udev_device->driver);
+        udev_device->driver = strdup(driver);
+        if (udev_device->driver == NULL)
+                return -ENOMEM;
+        udev_device->driver_set = true;
+        udev_device_add_property(udev_device, "DRIVER", udev_device->driver);
+        return 0;
+}
+
+/**
+ * udev_device_get_devtype:
+ * @udev_device: udev device
+ *
+ * Retrieve the devtype string of the udev device.
+ *
+ * Returns: the devtype name of the udev device, or #NULL if it can not be determined
+ **/
+UDEV_EXPORT const char *udev_device_get_devtype(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->devtype_set) {
+                udev_device->devtype_set = true;
+                udev_device_read_uevent_file(udev_device);
+        }
+        return udev_device->devtype;
+}
+
+static int udev_device_set_devtype(struct udev_device *udev_device, const char *devtype)
+{
+        free(udev_device->devtype);
+        udev_device->devtype = strdup(devtype);
+        if (udev_device->devtype == NULL)
+                return -ENOMEM;
+        udev_device->devtype_set = true;
+        udev_device_add_property(udev_device, "DEVTYPE", udev_device->devtype);
+        return 0;
+}
+
+static int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsystem)
+{
+        free(udev_device->subsystem);
+        udev_device->subsystem = strdup(subsystem);
+        if (udev_device->subsystem == NULL)
+                return -ENOMEM;
+        udev_device->subsystem_set = true;
+        udev_device_add_property(udev_device, "SUBSYSTEM", udev_device->subsystem);
+        return 0;
+}
+
+/**
+ * udev_device_get_subsystem:
+ * @udev_device: udev device
+ *
+ * Retrieve the subsystem string of the udev device. The string does not
+ * contain any "/".
+ *
+ * Returns: the subsystem name of the udev device, or #NULL if it can not be determined
+ **/
+UDEV_EXPORT const char *udev_device_get_subsystem(struct udev_device *udev_device)
+{
+        char subsystem[UTIL_NAME_SIZE];
+
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->subsystem_set) {
+                udev_device->subsystem_set = true;
+                /* read "subsystem" link */
+                if (util_get_sys_core_link_value(udev_device->udev, "subsystem", udev_device->syspath, subsystem, sizeof(subsystem)) > 0) {
+                        udev_device_set_subsystem(udev_device, subsystem);
+                        return udev_device->subsystem;
+                }
+                /* implicit names */
+                if (strncmp(udev_device->devpath, "/module/", 8) == 0) {
+                        udev_device_set_subsystem(udev_device, "module");
+                        return udev_device->subsystem;
+                }
+                if (strstr(udev_device->devpath, "/drivers/") != NULL) {
+                        udev_device_set_subsystem(udev_device, "drivers");
+                        return udev_device->subsystem;
+                }
+                if (strncmp(udev_device->devpath, "/subsystem/", 11) == 0 ||
+                    strncmp(udev_device->devpath, "/class/", 7) == 0 ||
+                    strncmp(udev_device->devpath, "/bus/", 5) == 0) {
+                        udev_device_set_subsystem(udev_device, "subsystem");
+                        return udev_device->subsystem;
+                }
+        }
+        return udev_device->subsystem;
+}
+
+mode_t udev_device_get_devnode_mode(struct udev_device *udev_device)
+{
+        if (!udev_device->info_loaded)
+                udev_device_read_uevent_file(udev_device);
+        return udev_device->devnode_mode;
+}
+
+static int udev_device_set_devnode_mode(struct udev_device *udev_device, mode_t mode)
+{
+        char num[32];
+
+        udev_device->devnode_mode = mode;
+        snprintf(num, sizeof(num), "%#o", mode);
+        udev_device_add_property(udev_device, "DEVMODE", num);
+        return 0;
+}
+
+struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value)
+{
+        udev_device->envp_uptodate = false;
+        if (value == NULL) {
+                struct udev_list_entry *list_entry;
+
+                list_entry = udev_device_get_properties_list_entry(udev_device);
+                list_entry = udev_list_entry_get_by_name(list_entry, key);
+                if (list_entry != NULL)
+                        udev_list_entry_delete(list_entry);
+                return NULL;
+        }
+        return udev_list_entry_add(&udev_device->properties_list, key, value);
+}
+
+static struct udev_list_entry *udev_device_add_property_from_string(struct udev_device *udev_device, const char *property)
+{
+        char name[UTIL_LINE_SIZE];
+        char *val;
+
+        util_strscpy(name, sizeof(name), property);
+        val = strchr(name, '=');
+        if (val == NULL)
+                return NULL;
+        val[0] = '\0';
+        val = &val[1];
+        if (val[0] == '\0')
+                val = NULL;
+        return udev_device_add_property(udev_device, name, val);
+}
+
+/*
+ * parse property string, and if needed, update internal values accordingly
+ *
+ * udev_device_add_property_from_string_parse_finish() needs to be
+ * called after adding properties, and its return value checked
+ *
+ * udev_device_set_info_loaded() needs to be set, to avoid trying
+ * to use a device without a DEVPATH set
+ */
+void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property)
+{
+        if (strncmp(property, "DEVPATH=", 8) == 0) {
+                char path[UTIL_PATH_SIZE];
+
+                util_strscpyl(path, sizeof(path), udev_get_sys_path(udev_device->udev), &property[8], NULL);
+                udev_device_set_syspath(udev_device, path);
+        } else if (strncmp(property, "SUBSYSTEM=", 10) == 0) {
+                udev_device_set_subsystem(udev_device, &property[10]);
+        } else if (strncmp(property, "DEVTYPE=", 8) == 0) {
+                udev_device_set_devtype(udev_device, &property[8]);
+        } else if (strncmp(property, "DEVNAME=", 8) == 0) {
+                udev_device_set_devnode(udev_device, &property[8]);
+        } else if (strncmp(property, "DEVLINKS=", 9) == 0) {
+                char devlinks[UTIL_PATH_SIZE];
+                char *slink;
+                char *next;
+
+                util_strscpy(devlinks, sizeof(devlinks), &property[9]);
+                slink = devlinks;
+                next = strchr(slink, ' ');
+                while (next != NULL) {
+                        next[0] = '\0';
+                        udev_device_add_devlink(udev_device, slink, 0);
+                        slink = &next[1];
+                        next = strchr(slink, ' ');
+                }
+                if (slink[0] != '\0')
+                        udev_device_add_devlink(udev_device, slink, 0);
+        } else if (strncmp(property, "TAGS=", 5) == 0) {
+                char tags[UTIL_PATH_SIZE];
+                char *next;
+
+                util_strscpy(tags, sizeof(tags), &property[5]);
+                next = strchr(tags, ':');
+                if (next != NULL) {
+                        next++;
+                        while (next[0] != '\0') {
+                                char *tag;
+
+                                tag = next;
+                                next = strchr(tag, ':');
+                                if (next == NULL)
+                                        break;
+                                next[0] = '\0';
+                                next++;
+                                udev_device_add_tag(udev_device, tag);
+                        }
+                }
+        } else if (strncmp(property, "USEC_INITIALIZED=", 19) == 0) {
+                udev_device_set_usec_initialized(udev_device, strtoull(&property[19], NULL, 10));
+        } else if (strncmp(property, "DRIVER=", 7) == 0) {
+                udev_device_set_driver(udev_device, &property[7]);
+        } else if (strncmp(property, "ACTION=", 7) == 0) {
+                udev_device_set_action(udev_device, &property[7]);
+        } else if (strncmp(property, "MAJOR=", 6) == 0) {
+                udev_device->maj = strtoull(&property[6], NULL, 10);
+        } else if (strncmp(property, "MINOR=", 6) == 0) {
+                udev_device->min = strtoull(&property[6], NULL, 10);
+        } else if (strncmp(property, "DEVPATH_OLD=", 12) == 0) {
+                udev_device_set_devpath_old(udev_device, &property[12]);
+        } else if (strncmp(property, "SEQNUM=", 7) == 0) {
+                udev_device_set_seqnum(udev_device, strtoull(&property[7], NULL, 10));
+        } else if (strncmp(property, "IFINDEX=", 8) == 0) {
+                udev_device_set_ifindex(udev_device, strtoull(&property[8], NULL, 10));
+        } else if (strncmp(property, "DEVMODE=", 8) == 0) {
+                udev_device_set_devnode_mode(udev_device, strtoul(&property[8], NULL, 8));
+        } else {
+                udev_device_add_property_from_string(udev_device, property);
+        }
+}
+
+int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device)
+{
+        if (udev_device->maj > 0)
+                udev_device_set_devnum(udev_device, makedev(udev_device->maj, udev_device->min));
+        udev_device->maj = 0;
+        udev_device->min = 0;
+
+        if (udev_device->devpath == NULL || udev_device->subsystem == NULL)
+                return -EINVAL;
+        return 0;
+}
+
+/**
+ * udev_device_get_property_value:
+ * @udev_device: udev device
+ * @key: property name
+ *
+ * Returns: the value of a device property, or #NULL if there is no such property.
+ **/
+UDEV_EXPORT const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key)
+{
+        struct udev_list_entry *list_entry;
+
+        if (udev_device == NULL)
+                return NULL;
+        if (key == NULL)
+                return NULL;
+
+        list_entry = udev_device_get_properties_list_entry(udev_device);
+        list_entry = udev_list_entry_get_by_name(list_entry, key);
+        return udev_list_entry_get_value(list_entry);
+}
+
+int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
+{
+        char filename[UTIL_PATH_SIZE];
+        char line[UTIL_LINE_SIZE];
+        FILE *f;
+
+        /* providing a database file will always force-load it */
+        if (dbfile == NULL) {
+                const char *id;
+
+                if (udev_device->db_loaded)
+                        return 0;
+                udev_device->db_loaded = true;
+
+                id = udev_device_get_id_filename(udev_device);
+                if (id == NULL)
+                        return -1;
+                util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_device->udev), "/data/", id, NULL);
+                dbfile = filename;
+        }
+
+        f = fopen(dbfile, "re");
+        if (f == NULL) {
+                info(udev_device->udev, "no db file to read %s: %m\n", dbfile);
+                return -1;
+        }
+        udev_device->is_initialized = true;
+
+        while (fgets(line, sizeof(line), f)) {
+                ssize_t len;
+                const char *val;
+                struct udev_list_entry *entry;
+
+                len = strlen(line);
+                if (len < 4)
+                        break;
+                line[len-1] = '\0';
+                val = &line[2];
+                switch(line[0]) {
+                case 'S':
+                        util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/", val, NULL);
+                        udev_device_add_devlink(udev_device, filename, 0);
+                        break;
+                case 'L':
+                        udev_device_set_devlink_priority(udev_device, atoi(val));
+                        break;
+                case 'E':
+                        entry = udev_device_add_property_from_string(udev_device, val);
+                        udev_list_entry_set_num(entry, true);
+                        break;
+                case 'G':
+                        udev_device_add_tag(udev_device, val);
+                        break;
+                case 'W':
+                        udev_device_set_watch_handle(udev_device, atoi(val));
+                        break;
+                case 'I':
+                        udev_device_set_usec_initialized(udev_device, strtoull(val, NULL, 10));
+                        break;
+                }
+        }
+        fclose(f);
+
+        info(udev_device->udev, "device %p filled with db file data\n", udev_device);
+        return 0;
+}
+
+int udev_device_read_uevent_file(struct udev_device *udev_device)
+{
+        char filename[UTIL_PATH_SIZE];
+        FILE *f;
+        char line[UTIL_LINE_SIZE];
+        int maj = 0;
+        int min = 0;
+
+        if (udev_device->uevent_loaded)
+                return 0;
+
+        util_strscpyl(filename, sizeof(filename), udev_device->syspath, "/uevent", NULL);
+        f = fopen(filename, "re");
+        if (f == NULL)
+                return -1;
+        udev_device->uevent_loaded = true;
+
+        while (fgets(line, sizeof(line), f)) {
+                char *pos;
+
+                pos = strchr(line, '\n');
+                if (pos == NULL)
+                        continue;
+                pos[0] = '\0';
+
+                if (strncmp(line, "DEVTYPE=", 8) == 0) {
+                        udev_device_set_devtype(udev_device, &line[8]);
+                        continue;
+                }
+                if (strncmp(line, "IFINDEX=", 8) == 0) {
+                        udev_device_set_ifindex(udev_device, strtoull(&line[8], NULL, 10));
+                        continue;
+                }
+                if (strncmp(line, "DEVNAME=", 8) == 0) {
+                        udev_device_set_devnode(udev_device, &line[8]);
+                        continue;
+                }
+
+                if (strncmp(line, "MAJOR=", 6) == 0)
+                        maj = strtoull(&line[6], NULL, 10);
+                else if (strncmp(line, "MINOR=", 6) == 0)
+                        min = strtoull(&line[6], NULL, 10);
+                else if (strncmp(line, "DEVMODE=", 8) == 0)
+                        udev_device->devnode_mode = strtoul(&line[8], NULL, 8);
+
+                udev_device_add_property_from_string(udev_device, line);
+        }
+
+        udev_device->devnum = makedev(maj, min);
+        fclose(f);
+        return 0;
+}
+
+void udev_device_set_info_loaded(struct udev_device *device)
+{
+        device->info_loaded = true;
+}
+
+struct udev_device *udev_device_new(struct udev *udev)
+{
+        struct udev_device *udev_device;
+        struct udev_list_entry *list_entry;
+
+        if (udev == NULL)
+                return NULL;
+
+        udev_device = calloc(1, sizeof(struct udev_device));
+        if (udev_device == NULL)
+                return NULL;
+        udev_device->refcount = 1;
+        udev_device->udev = udev;
+        udev_list_init(udev, &udev_device->devlinks_list, true);
+        udev_list_init(udev, &udev_device->properties_list, true);
+        udev_list_init(udev, &udev_device->sysattr_value_list, true);
+        udev_list_init(udev, &udev_device->sysattr_list, false);
+        udev_list_init(udev, &udev_device->tags_list, true);
+        udev_device->watch_handle = -1;
+        /* copy global properties */
+        udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev))
+                udev_device_add_property(udev_device,
+                                         udev_list_entry_get_name(list_entry),
+                                         udev_list_entry_get_value(list_entry));
+        dbg(udev_device->udev, "udev_device: %p created\n", udev_device);
+        return udev_device;
+}
+
+/**
+ * udev_device_new_from_syspath:
+ * @udev: udev library context
+ * @syspath: sys device path including sys directory
+ *
+ * Create new udev device, and fill in information from the sys
+ * device and the udev database entry. The syspath is the absolute
+ * path to the device, including the sys mount point.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+UDEV_EXPORT struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath)
+{
+        size_t len;
+        const char *subdir;
+        char path[UTIL_PATH_SIZE];
+        char *pos;
+        struct stat statbuf;
+        struct udev_device *udev_device;
+
+        if (udev == NULL)
+                return NULL;
+        if (syspath == NULL)
+                return NULL;
+
+        /* path starts in sys */
+        len = strlen(udev_get_sys_path(udev));
+        if (strncmp(syspath, udev_get_sys_path(udev), len) != 0) {
+                info(udev, "not in sys :%s\n", syspath);
+                return NULL;
+        }
+
+        /* path is not a root directory */
+        subdir = &syspath[len+1];
+        pos = strrchr(subdir, '/');
+        if (pos == NULL || pos[1] == '\0' || pos < &subdir[2]) {
+                dbg(udev, "not a subdir :%s\n", syspath);
+                return NULL;
+        }
+
+        /* resolve possible symlink to real path */
+        util_strscpy(path, sizeof(path), syspath);
+        util_resolve_sys_link(udev, path, sizeof(path));
+
+        if (strncmp(&path[len], "/devices/", 9) == 0) {
+                char file[UTIL_PATH_SIZE];
+
+                /* all "devices" require a "uevent" file */
+                util_strscpyl(file, sizeof(file), path, "/uevent", NULL);
+                if (stat(file, &statbuf) != 0) {
+                        dbg(udev, "not a device: %s\n", syspath);
+                        return NULL;
+                }
+        } else {
+                /* everything else just needs to be a directory */
+                if (stat(path, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) {
+                        dbg(udev, "directory not found: %s\n", syspath);
+                        return NULL;
+                }
+        }
+
+        udev_device = udev_device_new(udev);
+        if (udev_device == NULL)
+                return NULL;
+
+        udev_device_set_syspath(udev_device, path);
+        info(udev, "device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device));
+
+        return udev_device;
+}
+
+/**
+ * udev_device_new_from_devnum:
+ * @udev: udev library context
+ * @type: char or block device
+ * @devnum: device major/minor number
+ *
+ * Create new udev device, and fill in information from the sys
+ * device and the udev database entry. The device is looked-up
+ * by its major/minor number and type. Character and block device
+ * numbers are not unique across the two types.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+UDEV_EXPORT struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum)
+{
+        char path[UTIL_PATH_SIZE];
+        const char *type_str;
+
+        if (type == 'b')
+                type_str = "block";
+        else if (type == 'c')
+                type_str = "char";
+        else
+                return NULL;
+
+        /* use /sys/dev/{block,char}/<maj>:<min> link */
+        snprintf(path, sizeof(path), "%s/dev/%s/%u:%u",
+                 udev_get_sys_path(udev), type_str, major(devnum), minor(devnum));
+        return udev_device_new_from_syspath(udev, path);
+}
+
+struct udev_device *udev_device_new_from_id_filename(struct udev *udev, char *id)
+{
+        char type;
+        int maj, min;
+        char subsys[UTIL_PATH_SIZE];
+        char *sysname;
+
+        switch(id[0]) {
+        case 'b':
+        case 'c':
+                if (sscanf(id, "%c%i:%i", &type, &maj, &min) != 3)
+                        return NULL;
+                return udev_device_new_from_devnum(udev, type, makedev(maj, min));
+        case 'n': {
+                int sk;
+                struct ifreq ifr;
+                struct udev_device *dev;
+                int ifindex;
+
+                ifindex = strtoul(&id[1], NULL, 10);
+                if (ifindex <= 0)
+                        return NULL;
+
+                sk = socket(PF_INET, SOCK_DGRAM, 0);
+                if (sk < 0)
+                        return NULL;
+                memset(&ifr, 0x00, sizeof(struct ifreq));
+                ifr.ifr_ifindex = ifindex;
+                if (ioctl(sk, SIOCGIFNAME, &ifr) != 0) {
+                        close(sk);
+                        return NULL;
+                }
+                close(sk);
+
+                dev = udev_device_new_from_subsystem_sysname(udev, "net", ifr.ifr_name);
+                if (dev == NULL)
+                        return NULL;
+                if (udev_device_get_ifindex(dev) == ifindex)
+                        return dev;
+                udev_device_unref(dev);
+                return NULL;
+        }
+        case '+':
+                util_strscpy(subsys, sizeof(subsys), &id[1]);
+                sysname = strchr(subsys, ':');
+                if (sysname == NULL)
+                        return NULL;
+                sysname[0] = '\0';
+                sysname = &sysname[1];
+                return udev_device_new_from_subsystem_sysname(udev, subsys, sysname);
+        default:
+                return NULL;
+        }
+}
+
+/**
+ * udev_device_new_from_subsystem_sysname:
+ * @udev: udev library context
+ * @subsystem: the subsystem of the device
+ * @sysname: the name of the device
+ *
+ * Create new udev device, and fill in information from the sys device
+ * and the udev database entry. The device is looked up by the subsystem
+ * and name string of the device, like "mem" / "zero", or "block" / "sda".
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+UDEV_EXPORT struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname)
+{
+        char path_full[UTIL_PATH_SIZE];
+        char *path;
+        size_t l;
+        struct stat statbuf;
+
+        path = path_full;
+        l = util_strpcpyl(&path, sizeof(path_full), udev_get_sys_path(udev), NULL);
+
+        if (strcmp(subsystem, "subsystem") == 0) {
+                util_strscpyl(path, l, "/subsystem/", sysname, NULL);
+                if (stat(path_full, &statbuf) == 0)
+                        goto found;
+
+                util_strscpyl(path, l, "/bus/", sysname, NULL);
+                if (stat(path_full, &statbuf) == 0)
+                        goto found;
+
+                util_strscpyl(path, l, "/class/", sysname, NULL);
+                if (stat(path_full, &statbuf) == 0)
+                        goto found;
+                goto out;
+        }
+
+        if (strcmp(subsystem, "module") == 0) {
+                util_strscpyl(path, l, "/module/", sysname, NULL);
+                if (stat(path_full, &statbuf) == 0)
+                        goto found;
+                goto out;
+        }
+
+        if (strcmp(subsystem, "drivers") == 0) {
+                char subsys[UTIL_NAME_SIZE];
+                char *driver;
+
+                util_strscpy(subsys, sizeof(subsys), sysname);
+                driver = strchr(subsys, ':');
+                if (driver != NULL) {
+                        driver[0] = '\0';
+                        driver = &driver[1];
+
+                        util_strscpyl(path, l, "/subsystem/", subsys, "/drivers/", driver, NULL);
+                        if (stat(path_full, &statbuf) == 0)
+                                goto found;
+
+                        util_strscpyl(path, l, "/bus/", subsys, "/drivers/", driver, NULL);
+                        if (stat(path_full, &statbuf) == 0)
+                                goto found;
+                }
+                goto out;
+        }
+
+        util_strscpyl(path, l, "/subsystem/", subsystem, "/devices/", sysname, NULL);
+        if (stat(path_full, &statbuf) == 0)
+                goto found;
+
+        util_strscpyl(path, l, "/bus/", subsystem, "/devices/", sysname, NULL);
+        if (stat(path_full, &statbuf) == 0)
+                goto found;
+
+        util_strscpyl(path, l, "/class/", subsystem, "/", sysname, NULL);
+        if (stat(path_full, &statbuf) == 0)
+                goto found;
+out:
+        return NULL;
+found:
+        return udev_device_new_from_syspath(udev, path_full);
+}
+
+/**
+ * udev_device_new_from_environment
+ * @udev: udev library context
+ *
+ * Create new udev device, and fill in information from the
+ * current process environment. This only works reliable if
+ * the process is called from a udev rule. It is usually used
+ * for tools executed from IMPORT= rules.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+UDEV_EXPORT struct udev_device *udev_device_new_from_environment(struct udev *udev)
+{
+        int i;
+        struct udev_device *udev_device;
+
+        udev_device = udev_device_new(udev);
+        if (udev_device == NULL)
+                return NULL;
+        udev_device_set_info_loaded(udev_device);
+
+        for (i = 0; environ[i] != NULL; i++)
+                udev_device_add_property_from_string_parse(udev_device, environ[i]);
+
+        if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) {
+                info(udev, "missing values, invalid device\n");
+                udev_device_unref(udev_device);
+                udev_device = NULL;
+        }
+
+        return udev_device;
+}
+
+static struct udev_device *device_new_from_parent(struct udev_device *udev_device)
+{
+        struct udev_device *udev_device_parent = NULL;
+        char path[UTIL_PATH_SIZE];
+        const char *subdir;
+
+        util_strscpy(path, sizeof(path), udev_device->syspath);
+        subdir = &path[strlen(udev_get_sys_path(udev_device->udev))+1];
+        for (;;) {
+                char *pos;
+
+                pos = strrchr(subdir, '/');
+                if (pos == NULL || pos < &subdir[2])
+                        break;
+                pos[0] = '\0';
+                udev_device_parent = udev_device_new_from_syspath(udev_device->udev, path);
+                if (udev_device_parent != NULL)
+                        return udev_device_parent;
+        }
+        return NULL;
+}
+
+/**
+ * udev_device_get_parent:
+ * @udev_device: the device to start searching from
+ *
+ * Find the next parent device, and fill in information from the sys
+ * device and the udev database entry.
+ *
+ * The returned the device is not referenced. It is attached to the
+ * child device, and will be cleaned up when the child device
+ * is cleaned up.
+ *
+ * It is not necessarily just the upper level directory, empty or not
+ * recognized sys directories are ignored.
+ *
+ * It can be called as many times as needed, without caring about
+ * references.
+ *
+ * Returns: a new udev device, or #NULL, if it no parent exist.
+ **/
+UDEV_EXPORT struct udev_device *udev_device_get_parent(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->parent_set) {
+                udev_device->parent_set = true;
+                udev_device->parent_device = device_new_from_parent(udev_device);
+        }
+        if (udev_device->parent_device != NULL)
+                dbg(udev_device->udev, "returning existing parent %p\n", udev_device->parent_device);
+        return udev_device->parent_device;
+}
+
+/**
+ * udev_device_get_parent_with_subsystem_devtype:
+ * @udev_device: udev device to start searching from
+ * @subsystem: the subsystem of the device
+ * @devtype: the type (DEVTYPE) of the device
+ *
+ * Find the next parent device, with a matching subsystem and devtype
+ * value, and fill in information from the sys device and the udev
+ * database entry.
+ *
+ * If devtype is #NULL, only subsystem is checked, and any devtype will
+ * match.
+ *
+ * The returned the device is not referenced. It is attached to the
+ * child device, and will be cleaned up when the child device
+ * is cleaned up.
+ *
+ * It can be called as many times as needed, without caring about
+ * references.
+ *
+ * Returns: a new udev device, or #NULL if no matching parent exists.
+ **/
+UDEV_EXPORT struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype)
+{
+        struct udev_device *parent;
+
+        if (subsystem == NULL)
+                return NULL;
+
+        parent = udev_device_get_parent(udev_device);
+        while (parent != NULL) {
+                const char *parent_subsystem;
+                const char *parent_devtype;
+
+                parent_subsystem = udev_device_get_subsystem(parent);
+                if (parent_subsystem != NULL && strcmp(parent_subsystem, subsystem) == 0) {
+                        if (devtype == NULL)
+                                break;
+                        parent_devtype = udev_device_get_devtype(parent);
+                        if (parent_devtype != NULL && strcmp(parent_devtype, devtype) == 0)
+                                break;
+                }
+                parent = udev_device_get_parent(parent);
+        }
+        return parent;
+}
+
+/**
+ * udev_device_get_udev:
+ * @udev_device: udev device
+ *
+ * Retrieve the udev library context the device was created with.
+ *
+ * Returns: the udev library context
+ **/
+UDEV_EXPORT struct udev *udev_device_get_udev(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        return udev_device->udev;
+}
+
+/**
+ * udev_device_ref:
+ * @udev_device: udev device
+ *
+ * Take a reference of a udev device.
+ *
+ * Returns: the passed udev device
+ **/
+UDEV_EXPORT struct udev_device *udev_device_ref(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        udev_device->refcount++;
+        return udev_device;
+}
+
+/**
+ * udev_device_unref:
+ * @udev_device: udev device
+ *
+ * Drop a reference of a udev device. If the refcount reaches zero,
+ * the resources of the device will be released.
+ *
+ **/
+UDEV_EXPORT void udev_device_unref(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return;
+        udev_device->refcount--;
+        if (udev_device->refcount > 0)
+                return;
+        if (udev_device->parent_device != NULL)
+                udev_device_unref(udev_device->parent_device);
+        free(udev_device->syspath);
+        free(udev_device->sysname);
+        free(udev_device->devnode);
+        free(udev_device->subsystem);
+        free(udev_device->devtype);
+        udev_list_cleanup(&udev_device->devlinks_list);
+        udev_list_cleanup(&udev_device->properties_list);
+        udev_list_cleanup(&udev_device->sysattr_value_list);
+        udev_list_cleanup(&udev_device->sysattr_list);
+        udev_list_cleanup(&udev_device->tags_list);
+        free(udev_device->action);
+        free(udev_device->driver);
+        free(udev_device->devpath_old);
+        free(udev_device->id_filename);
+        free(udev_device->envp);
+        free(udev_device->monitor_buf);
+        dbg(udev_device->udev, "udev_device: %p released\n", udev_device);
+        free(udev_device);
+}
+
+/**
+ * udev_device_get_devpath:
+ * @udev_device: udev device
+ *
+ * Retrieve the kernel devpath value of the udev device. The path
+ * does not contain the sys mount point, and starts with a '/'.
+ *
+ * Returns: the devpath of the udev device
+ **/
+UDEV_EXPORT const char *udev_device_get_devpath(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        return udev_device->devpath;
+}
+
+/**
+ * udev_device_get_syspath:
+ * @udev_device: udev device
+ *
+ * Retrieve the sys path of the udev device. The path is an
+ * absolute path and starts with the sys mount point.
+ *
+ * Returns: the sys path of the udev device
+ **/
+UDEV_EXPORT const char *udev_device_get_syspath(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        return udev_device->syspath;
+}
+
+/**
+ * udev_device_get_sysname:
+ * @udev_device: udev device
+ *
+ * Returns: the sys name of the device device
+ **/
+UDEV_EXPORT const char *udev_device_get_sysname(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        return udev_device->sysname;
+}
+
+/**
+ * udev_device_get_sysnum:
+ * @udev_device: udev device
+ *
+ * Returns: the trailing number of of the device name
+ **/
+UDEV_EXPORT const char *udev_device_get_sysnum(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        return udev_device->sysnum;
+}
+
+/**
+ * udev_device_get_devnode:
+ * @udev_device: udev device
+ *
+ * Retrieve the device node file name belonging to the udev device.
+ * The path is an absolute path, and starts with the device directory.
+ *
+ * Returns: the device node file name of the udev device, or #NULL if no device node exists
+ **/
+UDEV_EXPORT const char *udev_device_get_devnode(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        if (udev_device->devnode != NULL)
+                return udev_device->devnode;
+        if (!udev_device->info_loaded)
+                udev_device_read_uevent_file(udev_device);
+        return udev_device->devnode;
+}
+
+/**
+ * udev_device_get_devlinks_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of device links pointing to the device file of
+ * the udev device. The next list entry can be retrieved with
+ * udev_list_entry_next(), which returns #NULL if no more entries exist.
+ * The devlink path can be retrieved from the list entry by
+ * udev_list_entry_get_name(). The path is an absolute path, and starts with
+ * the device directory.
+ *
+ * Returns: the first entry of the device node link list
+ **/
+UDEV_EXPORT struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        return udev_list_get_entry(&udev_device->devlinks_list);
+}
+
+void udev_device_cleanup_devlinks_list(struct udev_device *udev_device)
+{
+        udev_device->devlinks_uptodate = false;
+        udev_list_cleanup(&udev_device->devlinks_list);
+}
+
+/**
+ * udev_device_get_properties_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of key/value device properties of the udev
+ * device. The next list entry can be retrieved with udev_list_entry_next(),
+ * which returns #NULL if no more entries exist. The property name
+ * can be retrieved from the list entry by udev_list_get_name(),
+ * the property value by udev_list_get_value().
+ *
+ * Returns: the first entry of the property list
+ **/
+UDEV_EXPORT struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->info_loaded) {
+                udev_device_read_uevent_file(udev_device);
+                udev_device_read_db(udev_device, NULL);
+        }
+        if (!udev_device->devlinks_uptodate) {
+                char symlinks[UTIL_PATH_SIZE];
+                struct udev_list_entry *list_entry;
+
+                udev_device->devlinks_uptodate = true;
+                list_entry = udev_device_get_devlinks_list_entry(udev_device);
+                if (list_entry != NULL) {
+                        char *s;
+                        size_t l;
+
+                        s = symlinks;
+                        l = util_strpcpyl(&s, sizeof(symlinks), udev_list_entry_get_name(list_entry), NULL);
+                        udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry))
+                                l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry), NULL);
+                        udev_device_add_property(udev_device, "DEVLINKS", symlinks);
+                }
+        }
+        if (!udev_device->tags_uptodate) {
+                udev_device->tags_uptodate = true;
+                if (udev_device_get_tags_list_entry(udev_device) != NULL) {
+                        char tags[UTIL_PATH_SIZE];
+                        struct udev_list_entry *list_entry;
+                        char *s;
+                        size_t l;
+
+                        s = tags;
+                        l = util_strpcpyl(&s, sizeof(tags), ":", NULL);
+                        udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device))
+                                l = util_strpcpyl(&s, l, udev_list_entry_get_name(list_entry), ":", NULL);
+                        udev_device_add_property(udev_device, "TAGS", tags);
+                }
+        }
+        return udev_list_get_entry(&udev_device->properties_list);
+}
+
+/**
+ * udev_device_get_action:
+ * @udev_device: udev device
+ *
+ * This is only valid if the device was received through a monitor. Devices read from
+ * sys do not have an action string. Usual actions are: add, remove, change, online,
+ * offline.
+ *
+ * Returns: the kernel action value, or #NULL if there is no action value available.
+ **/
+UDEV_EXPORT const char *udev_device_get_action(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        return udev_device->action;
+}
+
+/**
+ * udev_device_get_usec_since_initialized:
+ * @udev_device: udev device
+ *
+ * Return the number of microseconds passed since udev set up the
+ * device for the first time.
+ *
+ * This is only implemented for devices with need to store properties
+ * in the udev database. All other devices return 0 here.
+ *
+ * Returns: the number of microseconds since the device was first seen.
+ **/
+UDEV_EXPORT unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device)
+{
+        unsigned long long now;
+
+        if (udev_device == NULL)
+                return 0;
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        if (udev_device->usec_initialized == 0)
+                return 0;
+        now = now_usec();
+        if (now == 0)
+                return 0;
+        return now - udev_device->usec_initialized;
+}
+
+unsigned long long udev_device_get_usec_initialized(struct udev_device *udev_device)
+{
+        return udev_device->usec_initialized;
+}
+
+void udev_device_set_usec_initialized(struct udev_device *udev_device, unsigned long long usec_initialized)
+{
+        char num[32];
+
+        udev_device->usec_initialized = usec_initialized;
+        snprintf(num, sizeof(num), "%llu", usec_initialized);
+        udev_device_add_property(udev_device, "USEC_INITIALIZED", num);
+}
+
+/**
+ * udev_device_get_sysattr_value:
+ * @udev_device: udev device
+ * @sysattr: attribute name
+ *
+ * The retrieved value is cached in the device. Repeated calls will return the same
+ * value and not open the attribute again.
+ *
+ * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value.
+ **/
+UDEV_EXPORT const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr)
+{
+        struct udev_list_entry *list_entry;
+        char path[UTIL_PATH_SIZE];
+        char value[4096];
+        struct stat statbuf;
+        int fd;
+        ssize_t size;
+        const char *val = NULL;
+
+        if (udev_device == NULL)
+                return NULL;
+        if (sysattr == NULL)
+                return NULL;
+
+        /* look for possibly already cached result */
+        list_entry = udev_list_get_entry(&udev_device->sysattr_value_list);
+        list_entry = udev_list_entry_get_by_name(list_entry, sysattr);
+        if (list_entry != NULL) {
+                dbg(udev_device->udev, "got '%s' (%s) from cache\n",
+                    sysattr, udev_list_entry_get_value(list_entry));
+                return udev_list_entry_get_value(list_entry);
+        }
+
+        util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", sysattr, NULL);
+        if (lstat(path, &statbuf) != 0) {
+                dbg(udev_device->udev, "no attribute '%s', keep negative entry\n", path);
+                udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, NULL);
+                goto out;
+        }
+
+        if (S_ISLNK(statbuf.st_mode)) {
+                struct udev_device *dev;
+
+                /*
+                 * Some core links return only the last element of the target path,
+                 * these are just values, the paths should not be exposed.
+                 */
+                if (strcmp(sysattr, "driver") == 0 ||
+                    strcmp(sysattr, "subsystem") == 0 ||
+                    strcmp(sysattr, "module") == 0) {
+                        if (util_get_sys_core_link_value(udev_device->udev, sysattr,
+                                                         udev_device->syspath, value, sizeof(value)) < 0)
+                                return NULL;
+                        dbg(udev_device->udev, "cache '%s' with link value '%s'\n", sysattr, value);
+                        list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, value);
+                        val = udev_list_entry_get_value(list_entry);
+                        goto out;
+                }
+
+                /* resolve link to a device and return its syspath */
+                util_strscpyl(path, sizeof(path), udev_device->syspath, "/", sysattr, NULL);
+                dev = udev_device_new_from_syspath(udev_device->udev, path);
+                if (dev != NULL) {
+                        list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr,
+                                                         udev_device_get_syspath(dev));
+                        val = udev_list_entry_get_value(list_entry);
+                        udev_device_unref(dev);
+                }
+
+                goto out;
+        }
+
+        /* skip directories */
+        if (S_ISDIR(statbuf.st_mode))
+                goto out;
+
+        /* skip non-readable files */
+        if ((statbuf.st_mode & S_IRUSR) == 0)
+                goto out;
+
+        /* read attribute value */
+        fd = open(path, O_RDONLY|O_CLOEXEC);
+        if (fd < 0) {
+                dbg(udev_device->udev, "attribute '%s' can not be opened\n", path);
+                goto out;
+        }
+        size = read(fd, value, sizeof(value));
+        close(fd);
+        if (size < 0)
+                goto out;
+        if (size == sizeof(value))
+                goto out;
+
+        /* got a valid value, store it in cache and return it */
+        value[size] = '\0';
+        util_remove_trailing_chars(value, '\n');
+        dbg(udev_device->udev, "'%s' has attribute value '%s'\n", path, value);
+        list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, value);
+        val = udev_list_entry_get_value(list_entry);
+out:
+        return val;
+}
+
+static int udev_device_sysattr_list_read(struct udev_device *udev_device)
+{
+        struct dirent *dent;
+        DIR *dir;
+        int num = 0;
+
+        if (udev_device == NULL)
+                return -1;
+        if (udev_device->sysattr_list_read)
+                return 0;
+
+        dir = opendir(udev_device_get_syspath(udev_device));
+        if (!dir) {
+                dbg(udev_device->udev, "sysfs dir '%s' can not be opened\n",
+                                udev_device_get_syspath(udev_device));
+                return -1;
+        }
+
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                char path[UTIL_PATH_SIZE];
+                struct stat statbuf;
+
+                /* only handle symlinks and regular files */
+                if (dent->d_type != DT_LNK && dent->d_type != DT_REG)
+                        continue;
+
+                util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", dent->d_name, NULL);
+                if (lstat(path, &statbuf) != 0)
+                        continue;
+                if ((statbuf.st_mode & S_IRUSR) == 0)
+                        continue;
+
+                udev_list_entry_add(&udev_device->sysattr_list, dent->d_name, NULL);
+                num++;
+        }
+
+        closedir(dir);
+        dbg(udev_device->udev, "found %d sysattrs for '%s'\n", num, udev_device_get_syspath(udev_device));
+        udev_device->sysattr_list_read = true;
+
+        return num;
+}
+
+/**
+ * udev_device_get_sysattr_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of available sysattrs, with value being empty;
+ * This just return all available sysfs attributes for a particular
+ * device without reading their values.
+ *
+ * Returns: the first entry of the property list
+ **/
+UDEV_EXPORT struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device)
+{
+        if (!udev_device->sysattr_list_read) {
+                int ret;
+                ret = udev_device_sysattr_list_read(udev_device);
+                if (0 > ret)
+                        return NULL;
+        }
+
+        return udev_list_get_entry(&udev_device->sysattr_list);
+}
+
+int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath)
+{
+        const char *pos;
+        size_t len;
+
+        free(udev_device->syspath);
+        udev_device->syspath = strdup(syspath);
+        if (udev_device->syspath ==  NULL)
+                return -ENOMEM;
+        udev_device->devpath = &udev_device->syspath[strlen(udev_get_sys_path(udev_device->udev))];
+        udev_device_add_property(udev_device, "DEVPATH", udev_device->devpath);
+
+        pos = strrchr(udev_device->syspath, '/');
+        if (pos == NULL)
+                return -EINVAL;
+        udev_device->sysname = strdup(&pos[1]);
+        if (udev_device->sysname == NULL)
+                return -ENOMEM;
+
+        /* some devices have '!' in their name, change that to '/' */
+        len = 0;
+        while (udev_device->sysname[len] != '\0') {
+                if (udev_device->sysname[len] == '!')
+                        udev_device->sysname[len] = '/';
+                len++;
+        }
+
+        /* trailing number */
+        while (len > 0 && isdigit(udev_device->sysname[--len]))
+                udev_device->sysnum = &udev_device->sysname[len];
+
+        /* sysname is completely numeric */
+        if (len == 0)
+                udev_device->sysnum = NULL;
+
+        return 0;
+}
+
+int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode)
+{
+        free(udev_device->devnode);
+        if (devnode[0] != '/') {
+                if (asprintf(&udev_device->devnode, "%s/%s", udev_get_dev_path(udev_device->udev), devnode) < 0)
+                        udev_device->devnode = NULL;
+        } else {
+                udev_device->devnode = strdup(devnode);
+        }
+        if (udev_device->devnode == NULL)
+                return -ENOMEM;
+        udev_device_add_property(udev_device, "DEVNAME", udev_device->devnode);
+        return 0;
+}
+
+int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink, int unique)
+{
+        struct udev_list_entry *list_entry;
+
+        udev_device->devlinks_uptodate = false;
+        list_entry = udev_list_entry_add(&udev_device->devlinks_list, devlink, NULL);
+        if (list_entry == NULL)
+                return -ENOMEM;
+        if (unique)
+                udev_list_entry_set_num(list_entry, true);
+        return 0;
+}
+
+const char *udev_device_get_id_filename(struct udev_device *udev_device)
+{
+        if (udev_device->id_filename == NULL) {
+                if (udev_device_get_subsystem(udev_device) == NULL)
+                        return NULL;
+
+                if (major(udev_device_get_devnum(udev_device)) > 0) {
+                        /* use dev_t -- b259:131072, c254:0 */
+                        if (asprintf(&udev_device->id_filename, "%c%u:%u",
+                                     strcmp(udev_device_get_subsystem(udev_device), "block") == 0 ? 'b' : 'c',
+                                     major(udev_device_get_devnum(udev_device)),
+                                     minor(udev_device_get_devnum(udev_device))) < 0)
+                                udev_device->id_filename = NULL;
+                } else if (udev_device_get_ifindex(udev_device) > 0) {
+                        /* use netdev ifindex -- n3 */
+                        if (asprintf(&udev_device->id_filename, "n%u", udev_device_get_ifindex(udev_device)) < 0)
+                                udev_device->id_filename = NULL;
+                } else {
+                        /*
+                         * use $subsys:$syname -- pci:0000:00:1f.2
+                         * sysname() has '!' translated, get it from devpath
+                         */
+                        const char *sysname;
+                        sysname = strrchr(udev_device->devpath, '/');
+                        if (sysname == NULL)
+                                return NULL;
+                        sysname = &sysname[1];
+                        if (asprintf(&udev_device->id_filename, "+%s:%s", udev_device_get_subsystem(udev_device), sysname) < 0)
+                                udev_device->id_filename = NULL;
+                }
+        }
+        return udev_device->id_filename;
+}
+
+/**
+ * udev_device_get_is_initialized:
+ * @udev_device: udev device
+ *
+ * Check if udev has already handled the device and has set up
+ * device node permissions and context, or has renamed a network
+ * device.
+ *
+ * This is only implemented for devices with a device node
+ * or network interfaces. All other devices return 1 here.
+ *
+ * Returns: 1 if the device is set up. 0 otherwise.
+ **/
+UDEV_EXPORT int udev_device_get_is_initialized(struct udev_device *udev_device)
+{
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        return udev_device->is_initialized;
+}
+
+void udev_device_set_is_initialized(struct udev_device *udev_device)
+{
+        udev_device->is_initialized = true;
+}
+
+int udev_device_add_tag(struct udev_device *udev_device, const char *tag)
+{
+        if (strchr(tag, ':') != NULL || strchr(tag, ' ') != NULL)
+                return -EINVAL;
+        udev_device->tags_uptodate = false;
+        if (udev_list_entry_add(&udev_device->tags_list, tag, NULL) != NULL)
+                return 0;
+        return -ENOMEM;
+}
+
+void udev_device_cleanup_tags_list(struct udev_device *udev_device)
+{
+        udev_device->tags_uptodate = false;
+        udev_list_cleanup(&udev_device->tags_list);
+}
+
+/**
+ * udev_device_get_tags_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of tags attached to the udev device. The next
+ * list entry can be retrieved with udev_list_entry_next(),
+ * which returns #NULL if no more entries exist. The tag string
+ * can be retrieved from the list entry by udev_list_get_name().
+ *
+ * Returns: the first entry of the tag list
+ **/
+UDEV_EXPORT struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device)
+{
+        if (udev_device == NULL)
+                return NULL;
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        return udev_list_get_entry(&udev_device->tags_list);
+}
+
+UDEV_EXPORT int udev_device_has_tag(struct udev_device *udev_device, const char *tag)
+{
+        struct udev_list_entry *list_entry;
+
+        if (udev_device == NULL)
+                return false;
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        list_entry = udev_device_get_tags_list_entry(udev_device);
+        if (udev_list_entry_get_by_name(list_entry, tag) != NULL)
+                return true;
+        return false;
+}
+
+#define ENVP_SIZE                        128
+#define MONITOR_BUF_SIZE                4096
+static int update_envp_monitor_buf(struct udev_device *udev_device)
+{
+        struct udev_list_entry *list_entry;
+        char *s;
+        size_t l;
+        unsigned int i;
+
+        /* monitor buffer of property strings */
+        free(udev_device->monitor_buf);
+        udev_device->monitor_buf_len = 0;
+        udev_device->monitor_buf = malloc(MONITOR_BUF_SIZE);
+        if (udev_device->monitor_buf == NULL)
+                return -ENOMEM;
+
+        /* envp array, strings will point into monitor buffer */
+        if (udev_device->envp == NULL)
+                udev_device->envp = malloc(sizeof(char *) * ENVP_SIZE);
+        if (udev_device->envp == NULL)
+                return -ENOMEM;
+
+        i = 0;
+        s = udev_device->monitor_buf;
+        l = MONITOR_BUF_SIZE;
+        udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device)) {
+                const char *key;
+
+                key = udev_list_entry_get_name(list_entry);
+                /* skip private variables */
+                if (key[0] == '.')
+                        continue;
+
+                /* add string to envp array */
+                udev_device->envp[i++] = s;
+                if (i+1 >= ENVP_SIZE)
+                        return -EINVAL;
+
+                /* add property string to monitor buffer */
+                l = util_strpcpyl(&s, l, key, "=", udev_list_entry_get_value(list_entry), NULL);
+                if (l == 0)
+                        return -EINVAL;
+                /* advance past the trailing '\0' that util_strpcpyl() guarantees */
+                s++;
+                l--;
+        }
+        udev_device->envp[i] = NULL;
+        udev_device->monitor_buf_len = s - udev_device->monitor_buf;
+        udev_device->envp_uptodate = true;
+        dbg(udev_device->udev, "filled envp/monitor buffer, %u properties, %zu bytes\n",
+            i, udev_device->monitor_buf_len);
+        return 0;
+}
+
+char **udev_device_get_properties_envp(struct udev_device *udev_device)
+{
+        if (!udev_device->envp_uptodate)
+                if (update_envp_monitor_buf(udev_device) != 0)
+                        return NULL;
+        return udev_device->envp;
+}
+
+ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf)
+{
+        if (!udev_device->envp_uptodate)
+                if (update_envp_monitor_buf(udev_device) != 0)
+                        return -EINVAL;
+        *buf = udev_device->monitor_buf;
+        return udev_device->monitor_buf_len;
+}
+
+int udev_device_set_action(struct udev_device *udev_device, const char *action)
+{
+        free(udev_device->action);
+        udev_device->action = strdup(action);
+        if (udev_device->action == NULL)
+                return -ENOMEM;
+        udev_device_add_property(udev_device, "ACTION", udev_device->action);
+        return 0;
+}
+
+int udev_device_get_devlink_priority(struct udev_device *udev_device)
+{
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        return udev_device->devlink_priority;
+}
+
+int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio)
+{
+         udev_device->devlink_priority = prio;
+        return 0;
+}
+
+int udev_device_get_watch_handle(struct udev_device *udev_device)
+{
+        if (!udev_device->info_loaded)
+                udev_device_read_db(udev_device, NULL);
+        return udev_device->watch_handle;
+}
+
+int udev_device_set_watch_handle(struct udev_device *udev_device, int handle)
+{
+        udev_device->watch_handle = handle;
+        return 0;
+}
+
+bool udev_device_get_db_persist(struct udev_device *udev_device)
+{
+        return udev_device->db_persist;
+}
+
+void udev_device_set_db_persist(struct udev_device *udev_device)
+{
+        udev_device->db_persist = true;
+}
diff --git a/src/libudev-enumerate.c b/src/libudev-enumerate.c
new file mode 100644
index 0000000..034d96f
--- /dev/null
+++ b/src/libudev-enumerate.c
@@ -0,0 +1,947 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-enumerate
+ * @short_description: lookup and sort sys devices
+ *
+ * Lookup devices in the sys filesystem, filter devices by properties,
+ * and return a sorted list of devices.
+ */
+
+struct syspath {
+        char *syspath;
+        size_t len;
+};
+
+/**
+ * udev_enumerate:
+ *
+ * Opaque object representing one device lookup/sort context.
+ */
+struct udev_enumerate {
+        struct udev *udev;
+        int refcount;
+        struct udev_list sysattr_match_list;
+        struct udev_list sysattr_nomatch_list;
+        struct udev_list subsystem_match_list;
+        struct udev_list subsystem_nomatch_list;
+        struct udev_list sysname_match_list;
+        struct udev_list properties_match_list;
+        struct udev_list tags_match_list;
+        struct udev_device *parent_match;
+        struct udev_list devices_list;
+        struct syspath *devices;
+        unsigned int devices_cur;
+        unsigned int devices_max;
+        bool devices_uptodate:1;
+        bool match_is_initialized;
+};
+
+/**
+ * udev_enumerate_new:
+ * @udev: udev library context
+ *
+ * Returns: an enumeration context
+ **/
+UDEV_EXPORT struct udev_enumerate *udev_enumerate_new(struct udev *udev)
+{
+        struct udev_enumerate *udev_enumerate;
+
+        udev_enumerate = calloc(1, sizeof(struct udev_enumerate));
+        if (udev_enumerate == NULL)
+                return NULL;
+        udev_enumerate->refcount = 1;
+        udev_enumerate->udev = udev;
+        udev_list_init(udev, &udev_enumerate->sysattr_match_list, false);
+        udev_list_init(udev, &udev_enumerate->sysattr_nomatch_list, false);
+        udev_list_init(udev, &udev_enumerate->subsystem_match_list, true);
+        udev_list_init(udev, &udev_enumerate->subsystem_nomatch_list, true);
+        udev_list_init(udev, &udev_enumerate->sysname_match_list, true);
+        udev_list_init(udev, &udev_enumerate->properties_match_list, false);
+        udev_list_init(udev, &udev_enumerate->tags_match_list, true);
+        udev_list_init(udev, &udev_enumerate->devices_list, false);
+        return udev_enumerate;
+}
+
+/**
+ * udev_enumerate_ref:
+ * @udev_enumerate: context
+ *
+ * Take a reference of a enumeration context.
+ *
+ * Returns: the passed enumeration context
+ **/
+UDEV_EXPORT struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate)
+{
+        if (udev_enumerate == NULL)
+                return NULL;
+        udev_enumerate->refcount++;
+        return udev_enumerate;
+}
+
+/**
+ * udev_enumerate_unref:
+ * @udev_enumerate: context
+ *
+ * Drop a reference of an enumeration context. If the refcount reaches zero,
+ * all resources of the enumeration context will be released.
+ **/
+UDEV_EXPORT void udev_enumerate_unref(struct udev_enumerate *udev_enumerate)
+{
+        unsigned int i;
+
+        if (udev_enumerate == NULL)
+                return;
+        udev_enumerate->refcount--;
+        if (udev_enumerate->refcount > 0)
+                return;
+        udev_list_cleanup(&udev_enumerate->sysattr_match_list);
+        udev_list_cleanup(&udev_enumerate->sysattr_nomatch_list);
+        udev_list_cleanup(&udev_enumerate->subsystem_match_list);
+        udev_list_cleanup(&udev_enumerate->subsystem_nomatch_list);
+        udev_list_cleanup(&udev_enumerate->sysname_match_list);
+        udev_list_cleanup(&udev_enumerate->properties_match_list);
+        udev_list_cleanup(&udev_enumerate->tags_match_list);
+        udev_device_unref(udev_enumerate->parent_match);
+        udev_list_cleanup(&udev_enumerate->devices_list);
+        for (i = 0; i < udev_enumerate->devices_cur; i++)
+                free(udev_enumerate->devices[i].syspath);
+        free(udev_enumerate->devices);
+        free(udev_enumerate);
+}
+
+/**
+ * udev_enumerate_get_udev:
+ * @udev_enumerate: context
+ *
+ * Returns: the udev library context.
+ */
+UDEV_EXPORT struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate)
+{
+        if (udev_enumerate == NULL)
+                return NULL;
+        return udev_enumerate->udev;
+}
+
+static int syspath_add(struct udev_enumerate *udev_enumerate, const char *syspath)
+{
+        char *path;
+        struct syspath *entry;
+
+        /* double array size if needed */
+        if (udev_enumerate->devices_cur >= udev_enumerate->devices_max) {
+                struct syspath *buf;
+                unsigned int add;
+
+                add = udev_enumerate->devices_max;
+                if (add < 1024)
+                        add = 1024;
+                buf = realloc(udev_enumerate->devices, (udev_enumerate->devices_max + add) * sizeof(struct syspath));
+                if (buf == NULL)
+                        return -ENOMEM;
+                udev_enumerate->devices = buf;
+                udev_enumerate->devices_max += add;
+        }
+
+        path = strdup(syspath);
+        if (path == NULL)
+                return -ENOMEM;
+        entry = &udev_enumerate->devices[udev_enumerate->devices_cur];
+        entry->syspath = path;
+        entry->len = strlen(path);
+        udev_enumerate->devices_cur++;
+        udev_enumerate->devices_uptodate = false;
+        return 0;
+}
+
+static int syspath_cmp(const void *p1, const void *p2)
+{
+        const struct syspath *path1 = p1;
+        const struct syspath *path2 = p2;
+        size_t len;
+        int ret;
+
+        len = MIN(path1->len, path2->len);
+        ret = memcmp(path1->syspath, path2->syspath, len);
+        if (ret == 0) {
+                if (path1->len < path2->len)
+                        ret = -1;
+                else if (path1->len > path2->len)
+                        ret = 1;
+        }
+        return ret;
+}
+
+/* For devices that should be moved to the absolute end of the list */
+static bool devices_delay_end(struct udev *udev, const char *syspath)
+{
+        static const char *delay_device_list[] = {
+                "/block/md",
+                "/block/dm-",
+                NULL
+        };
+        size_t len;
+        int i;
+
+        len = strlen(udev_get_sys_path(udev));
+        for (i = 0; delay_device_list[i] != NULL; i++) {
+                if (strstr(&syspath[len], delay_device_list[i]) != NULL) {
+                        dbg(udev, "delaying: %s\n", syspath);
+                        return true;
+                }
+        }
+        return false;
+}
+
+/* For devices that should just be moved a little bit later, just
+ * before the point where some common path prefix changes. Returns the
+ * number of characters that make up that common prefix */
+static size_t devices_delay_later(struct udev *udev, const char *syspath)
+{
+        const char *c;
+
+        /* For sound cards the control device must be enumerated last
+         * to make sure it's the final device node that gets ACLs
+         * applied. Applications rely on this fact and use ACL changes
+         * on the control node as an indicator that the ACL change of
+         * the entire sound card completed. The kernel makes this
+         * guarantee when creating those devices, and hence we should
+         * too when enumerating them. */
+
+        if ((c = strstr(syspath, "/sound/card"))) {
+                c += 11;
+                c += strcspn(c, "/");
+
+                if (strncmp(c, "/controlC", 9) == 0)
+                        return c - syspath + 1;
+        }
+
+        return 0;
+}
+
+/**
+ * udev_enumerate_get_list_entry:
+ * @udev_enumerate: context
+ *
+ * Returns: the first entry of the sorted list of device paths.
+ */
+UDEV_EXPORT struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate)
+{
+        if (udev_enumerate == NULL)
+                return NULL;
+        if (!udev_enumerate->devices_uptodate) {
+                unsigned int i;
+                unsigned int max;
+                struct syspath *prev = NULL, *move_later = NULL;
+                size_t move_later_prefix = 0;
+
+                udev_list_cleanup(&udev_enumerate->devices_list);
+                qsort(udev_enumerate->devices, udev_enumerate->devices_cur, sizeof(struct syspath), syspath_cmp);
+
+                max = udev_enumerate->devices_cur;
+                for (i = 0; i < max; i++) {
+                        struct syspath *entry = &udev_enumerate->devices[i];
+
+                        /* skip duplicated entries */
+                        if (prev != NULL &&
+                            entry->len == prev->len &&
+                            memcmp(entry->syspath, prev->syspath, entry->len) == 0)
+                                continue;
+                        prev = entry;
+
+                        /* skip to be delayed devices, and add them to the end of the list */
+                        if (devices_delay_end(udev_enumerate->udev, entry->syspath)) {
+                                syspath_add(udev_enumerate, entry->syspath);
+                                /* need to update prev here for the case realloc() gives a different address */
+                                prev = &udev_enumerate->devices[i];
+                                continue;
+                        }
+
+                        /* skip to be delayed devices, and move the to
+                         * the point where the prefix changes. We can
+                         * only move one item at a time. */
+                        if (!move_later) {
+                                move_later_prefix = devices_delay_later(udev_enumerate->udev, entry->syspath);
+
+                                if (move_later_prefix > 0) {
+                                        move_later = entry;
+                                        continue;
+                                }
+                        }
+
+                        if (move_later &&
+                            strncmp(entry->syspath, move_later->syspath, move_later_prefix) != 0) {
+
+                                udev_list_entry_add(&udev_enumerate->devices_list, move_later->syspath, NULL);
+                                move_later = NULL;
+                        }
+
+                        udev_list_entry_add(&udev_enumerate->devices_list, entry->syspath, NULL);
+                }
+
+                if (move_later)
+                        udev_list_entry_add(&udev_enumerate->devices_list, move_later->syspath, NULL);
+
+                /* add and cleanup delayed devices from end of list */
+                for (i = max; i < udev_enumerate->devices_cur; i++) {
+                        struct syspath *entry = &udev_enumerate->devices[i];
+
+                        udev_list_entry_add(&udev_enumerate->devices_list, entry->syspath, NULL);
+                        free(entry->syspath);
+                }
+                udev_enumerate->devices_cur = max;
+
+                udev_enumerate->devices_uptodate = true;
+        }
+        return udev_list_get_entry(&udev_enumerate->devices_list);
+}
+
+/**
+ * udev_enumerate_add_match_subsystem:
+ * @udev_enumerate: context
+ * @subsystem: filter for a subsystem of the device to include in the list
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (subsystem == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->subsystem_match_list, subsystem, NULL) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_nomatch_subsystem:
+ * @udev_enumerate: context
+ * @subsystem: filter for a subsystem of the device to exclude from the list
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (subsystem == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->subsystem_nomatch_list, subsystem, NULL) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_match_sysattr:
+ * @udev_enumerate: context
+ * @sysattr: filter for a sys attribute at the device to include in the list
+ * @value: optional value of the sys attribute
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (sysattr == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->sysattr_match_list, sysattr, value) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_nomatch_sysattr:
+ * @udev_enumerate: context
+ * @sysattr: filter for a sys attribute at the device to exclude from the list
+ * @value: optional value of the sys attribute
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (sysattr == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->sysattr_nomatch_list, sysattr, value) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+static int match_sysattr_value(struct udev_device *dev, const char *sysattr, const char *match_val)
+{
+        const char *val = NULL;
+        bool match = false;
+
+        val = udev_device_get_sysattr_value(dev, sysattr);
+        if (val == NULL)
+                goto exit;
+        if (match_val == NULL) {
+                match = true;
+                goto exit;
+        }
+        if (fnmatch(match_val, val, 0) == 0) {
+                match = true;
+                goto exit;
+        }
+exit:
+        return match;
+}
+
+/**
+ * udev_enumerate_add_match_property:
+ * @udev_enumerate: context
+ * @property: filter for a property of the device to include in the list
+ * @value: value of the property
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (property == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->properties_match_list, property, value) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_match_tag:
+ * @udev_enumerate: context
+ * @tag: filter for a tag of the device to include in the list
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (tag == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->tags_match_list, tag, NULL) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_match_parent:
+ * @udev_enumerate: context
+ * @parent: parent device where to start searching
+ *
+ * Return the devices on the subtree of one given device. The parent
+ * itself is included in the list.
+ *
+ * A reference for the device is held until the udev_enumerate context
+ * is cleaned up.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (parent == NULL)
+                return 0;
+        if (udev_enumerate->parent_match != NULL)
+                udev_device_unref(udev_enumerate->parent_match);
+        udev_enumerate->parent_match = udev_device_ref(parent);
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_match_is_initialized:
+ * @udev_enumerate: context
+ *
+ * Match only devices which udev has set up already. This makes
+ * sure, that the device node permissions and context are properly set
+ * and that network devices are fully renamed.
+ *
+ * Usually, devices which are found in the kernel but not already
+ * handled by udev, have still pending events. Services should subscribe
+ * to monitor events and wait for these devices to become ready, instead
+ * of using uninitialized devices.
+ *
+ * For now, this will not affect devices which do not have a device node
+ * and are not network interfaces.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        udev_enumerate->match_is_initialized = true;
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_match_sysname:
+ * @udev_enumerate: context
+ * @sysname: filter for the name of the device to include in the list
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (sysname == NULL)
+                return 0;
+        if (udev_list_entry_add(&udev_enumerate->sysname_match_list, sysname, NULL) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+static bool match_sysattr(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+        struct udev_list_entry *list_entry;
+
+        /* skip list */
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->sysattr_nomatch_list)) {
+                if (match_sysattr_value(dev, udev_list_entry_get_name(list_entry),
+                                        udev_list_entry_get_value(list_entry)))
+                        return false;
+        }
+        /* include list */
+        if (udev_list_get_entry(&udev_enumerate->sysattr_match_list) != NULL) {
+                udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->sysattr_match_list)) {
+                        /* anything that does not match, will make it FALSE */
+                        if (!match_sysattr_value(dev, udev_list_entry_get_name(list_entry),
+                                                 udev_list_entry_get_value(list_entry)))
+                                return false;
+                }
+                return true;
+        }
+        return true;
+}
+
+static bool match_property(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+        struct udev_list_entry *list_entry;
+        bool match = false;
+
+        /* no match always matches */
+        if (udev_list_get_entry(&udev_enumerate->properties_match_list) == NULL)
+                return true;
+
+        /* loop over matches */
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->properties_match_list)) {
+                const char *match_key = udev_list_entry_get_name(list_entry);
+                const char *match_value = udev_list_entry_get_value(list_entry);
+                struct udev_list_entry *property_entry;
+
+                /* loop over device properties */
+                udev_list_entry_foreach(property_entry, udev_device_get_properties_list_entry(dev)) {
+                        const char *dev_key = udev_list_entry_get_name(property_entry);
+                        const char *dev_value = udev_list_entry_get_value(property_entry);
+
+                        if (fnmatch(match_key, dev_key, 0) != 0)
+                                continue;
+                        if (match_value == NULL && dev_value == NULL) {
+                                match = true;
+                                goto out;
+                        }
+                        if (match_value == NULL || dev_value == NULL)
+                                continue;
+                        if (fnmatch(match_value, dev_value, 0) == 0) {
+                                match = true;
+                                goto out;
+                        }
+                }
+        }
+out:
+        return match;
+}
+
+static bool match_tag(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+        struct udev_list_entry *list_entry;
+
+        /* no match always matches */
+        if (udev_list_get_entry(&udev_enumerate->tags_match_list) == NULL)
+                return true;
+
+        /* loop over matches */
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->tags_match_list))
+                if (!udev_device_has_tag(dev, udev_list_entry_get_name(list_entry)))
+                        return false;
+
+        return true;
+}
+
+static bool match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+        const char *parent;
+
+        if (udev_enumerate->parent_match == NULL)
+                return true;
+
+        parent = udev_device_get_devpath(udev_enumerate->parent_match);
+        return strncmp(parent, udev_device_get_devpath(dev), strlen(parent)) == 0;
+}
+
+static bool match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname)
+{
+        struct udev_list_entry *list_entry;
+
+        if (udev_list_get_entry(&udev_enumerate->sysname_match_list) == NULL)
+                return true;
+
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->sysname_match_list)) {
+                if (fnmatch(udev_list_entry_get_name(list_entry), sysname, 0) != 0)
+                        continue;
+                return true;
+        }
+        return false;
+}
+
+static int scan_dir_and_add_devices(struct udev_enumerate *udev_enumerate,
+                                    const char *basedir, const char *subdir1, const char *subdir2)
+{
+        struct udev *udev = udev_enumerate_get_udev(udev_enumerate);
+        char path[UTIL_PATH_SIZE];
+        size_t l;
+        char *s;
+        DIR *dir;
+        struct dirent *dent;
+
+        s = path;
+        l = util_strpcpyl(&s, sizeof(path), udev_get_sys_path(udev), "/", basedir, NULL);
+        if (subdir1 != NULL)
+                l = util_strpcpyl(&s, l, "/", subdir1, NULL);
+        if (subdir2 != NULL)
+                util_strpcpyl(&s, l, "/", subdir2, NULL);
+        dir = opendir(path);
+        if (dir == NULL)
+                return -ENOENT;
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                char syspath[UTIL_PATH_SIZE];
+                struct udev_device *dev;
+
+                if (dent->d_name[0] == '.')
+                        continue;
+
+                if (!match_sysname(udev_enumerate, dent->d_name))
+                        continue;
+
+                util_strscpyl(syspath, sizeof(syspath), path, "/", dent->d_name, NULL);
+                dev = udev_device_new_from_syspath(udev_enumerate->udev, syspath);
+                if (dev == NULL)
+                        continue;
+
+                if (udev_enumerate->match_is_initialized) {
+                        /*
+                         * All devices with a device node or network interfaces
+                         * possibly need udev to adjust the device node permission
+                         * or context, or rename the interface before it can be
+                         * reliably used from other processes.
+                         *
+                         * For now, we can only check these types of devices, we
+                         * might not store a database, and have no way to find out
+                         * for all other types of devices.
+                         */
+                        if (!udev_device_get_is_initialized(dev) &&
+                            (major(udev_device_get_devnum(dev)) > 0 || udev_device_get_ifindex(dev) > 0))
+                                goto nomatch;
+                }
+                if (!match_parent(udev_enumerate, dev))
+                        goto nomatch;
+                if (!match_tag(udev_enumerate, dev))
+                        goto nomatch;
+                if (!match_property(udev_enumerate, dev))
+                        goto nomatch;
+                if (!match_sysattr(udev_enumerate, dev))
+                        goto nomatch;
+
+                syspath_add(udev_enumerate, udev_device_get_syspath(dev));
+nomatch:
+                udev_device_unref(dev);
+        }
+        closedir(dir);
+        return 0;
+}
+
+static bool match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+{
+        struct udev_list_entry *list_entry;
+
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->subsystem_nomatch_list)) {
+                if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0)
+                        return false;
+        }
+        if (udev_list_get_entry(&udev_enumerate->subsystem_match_list) != NULL) {
+                udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->subsystem_match_list)) {
+                        if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0)
+                                return true;
+                }
+                return false;
+        }
+        return true;
+}
+
+static int scan_dir(struct udev_enumerate *udev_enumerate, const char *basedir, const char *subdir, const char *subsystem)
+{
+        struct udev *udev = udev_enumerate_get_udev(udev_enumerate);
+
+        char path[UTIL_PATH_SIZE];
+        DIR *dir;
+        struct dirent *dent;
+
+        util_strscpyl(path, sizeof(path), udev_get_sys_path(udev), "/", basedir, NULL);
+        dir = opendir(path);
+        if (dir == NULL)
+                return -1;
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                if (dent->d_name[0] == '.')
+                        continue;
+                if (!match_subsystem(udev_enumerate, subsystem != NULL ? subsystem : dent->d_name))
+                        continue;
+                scan_dir_and_add_devices(udev_enumerate, basedir, dent->d_name, subdir);
+        }
+        closedir(dir);
+        return 0;
+}
+
+/**
+ * udev_enumerate_add_syspath:
+ * @udev_enumerate: context
+ * @syspath: path of a device
+ *
+ * Add a device to the list of devices, to retrieve it back sorted in dependency order.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath)
+{
+        struct udev_device *udev_device;
+
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+        if (syspath == NULL)
+                return 0;
+        /* resolve to real syspath */
+        udev_device = udev_device_new_from_syspath(udev_enumerate->udev, syspath);
+        if (udev_device == NULL)
+                return -EINVAL;
+        syspath_add(udev_enumerate, udev_device_get_syspath(udev_device));
+        udev_device_unref(udev_device);
+        return 0;
+}
+
+static int scan_devices_tags(struct udev_enumerate *udev_enumerate)
+{
+        struct udev *udev = udev_enumerate_get_udev(udev_enumerate);
+        struct udev_list_entry *list_entry;
+
+        /* scan only tagged devices, use tags reverse-index, instead of searching all devices in /sys */
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->tags_match_list)) {
+                DIR *dir;
+                struct dirent *dent;
+                char path[UTIL_PATH_SIZE];
+
+                util_strscpyl(path, sizeof(path), udev_get_run_path(udev), "/tags/",
+                              udev_list_entry_get_name(list_entry), NULL);
+                dir = opendir(path);
+                if (dir == NULL)
+                        continue;
+                for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                        struct udev_device *dev;
+
+                        if (dent->d_name[0] == '.')
+                                continue;
+
+                        dev = udev_device_new_from_id_filename(udev_enumerate->udev, dent->d_name);
+                        if (dev == NULL)
+                                continue;
+
+                        if (!match_subsystem(udev_enumerate, udev_device_get_subsystem(dev)))
+                                goto nomatch;
+                        if (!match_sysname(udev_enumerate, udev_device_get_sysname(dev)))
+                                goto nomatch;
+                        if (!match_parent(udev_enumerate, dev))
+                                goto nomatch;
+                        if (!match_property(udev_enumerate, dev))
+                                goto nomatch;
+                        if (!match_sysattr(udev_enumerate, dev))
+                                goto nomatch;
+
+                        syspath_add(udev_enumerate, udev_device_get_syspath(dev));
+nomatch:
+                        udev_device_unref(dev);
+                }
+                closedir(dir);
+        }
+        return 0;
+}
+
+static int parent_add_child(struct udev_enumerate *enumerate, const char *path)
+{
+        struct udev_device *dev;
+
+        dev = udev_device_new_from_syspath(enumerate->udev, path);
+        if (dev == NULL)
+                return -ENODEV;
+
+        if (!match_subsystem(enumerate, udev_device_get_subsystem(dev)))
+                return 0;
+        if (!match_sysname(enumerate, udev_device_get_sysname(dev)))
+                return 0;
+        if (!match_property(enumerate, dev))
+                return 0;
+        if (!match_sysattr(enumerate, dev))
+                return 0;
+
+        syspath_add(enumerate, udev_device_get_syspath(dev));
+        udev_device_unref(dev);
+        return 1;
+}
+
+static int parent_crawl_children(struct udev_enumerate *enumerate, const char *path, int maxdepth)
+{
+        DIR *d;
+        struct dirent *dent;
+
+        d = opendir(path);
+        if (d == NULL)
+                return -errno;
+
+        for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
+                char *child;
+
+                if (dent->d_name[0] == '.')
+                        continue;
+                if (dent->d_type != DT_DIR)
+                        continue;
+                if (asprintf(&child, "%s/%s", path, dent->d_name) < 0)
+                        continue;
+                parent_add_child(enumerate, child);
+                if (maxdepth > 0)
+                        parent_crawl_children(enumerate, child, maxdepth-1);
+                free(child);
+        }
+
+        closedir(d);
+        return 0;
+}
+
+static int scan_devices_children(struct udev_enumerate *enumerate)
+{
+        const char *path;
+
+        path = udev_device_get_syspath(enumerate->parent_match);
+        parent_add_child(enumerate, path);
+        return parent_crawl_children(enumerate, path, 256);
+}
+
+static int scan_devices_all(struct udev_enumerate *udev_enumerate)
+{
+        struct udev *udev = udev_enumerate_get_udev(udev_enumerate);
+        char base[UTIL_PATH_SIZE];
+        struct stat statbuf;
+
+        util_strscpyl(base, sizeof(base), udev_get_sys_path(udev), "/subsystem", NULL);
+        if (stat(base, &statbuf) == 0) {
+                /* we have /subsystem/, forget all the old stuff */
+                dbg(udev, "searching '/subsystem/*/devices/*' dir\n");
+                scan_dir(udev_enumerate, "subsystem", "devices", NULL);
+        } else {
+                dbg(udev, "searching '/bus/*/devices/*' dir\n");
+                scan_dir(udev_enumerate, "bus", "devices", NULL);
+                dbg(udev, "searching '/class/*' dir\n");
+                scan_dir(udev_enumerate, "class", NULL, NULL);
+        }
+        return 0;
+}
+
+/**
+ * udev_enumerate_scan_devices:
+ * @udev_enumerate: udev enumeration context
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ **/
+UDEV_EXPORT int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate)
+{
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+
+        /* efficiently lookup tags only, we maintain a reverse-index */
+        if (udev_list_get_entry(&udev_enumerate->tags_match_list) != NULL)
+                return scan_devices_tags(udev_enumerate);
+
+        /* walk the subtree of one parent device only */
+        if (udev_enumerate->parent_match != NULL)
+                return scan_devices_children(udev_enumerate);
+
+        /* scan devices of all subsystems */
+        return scan_devices_all(udev_enumerate);
+}
+
+/**
+ * udev_enumerate_scan_subsystems:
+ * @udev_enumerate: udev enumeration context
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ **/
+UDEV_EXPORT int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate)
+{
+        struct udev *udev = udev_enumerate_get_udev(udev_enumerate);
+        char base[UTIL_PATH_SIZE];
+        struct stat statbuf;
+        const char *subsysdir;
+
+        if (udev_enumerate == NULL)
+                return -EINVAL;
+
+        /* all kernel modules */
+        if (match_subsystem(udev_enumerate, "module")) {
+                dbg(udev, "searching 'modules/*' dir\n");
+                scan_dir_and_add_devices(udev_enumerate, "module", NULL, NULL);
+        }
+
+        util_strscpyl(base, sizeof(base), udev_get_sys_path(udev), "/subsystem", NULL);
+        if (stat(base, &statbuf) == 0)
+                subsysdir = "subsystem";
+        else
+                subsysdir = "bus";
+
+        /* all subsystems (only buses support coldplug) */
+        if (match_subsystem(udev_enumerate, "subsystem")) {
+                dbg(udev, "searching '%s/*' dir\n", subsysdir);
+                scan_dir_and_add_devices(udev_enumerate, subsysdir, NULL, NULL);
+        }
+
+        /* all subsystem drivers */
+        if (match_subsystem(udev_enumerate, "drivers")) {
+                dbg(udev, "searching '%s/*/drivers/*' dir\n", subsysdir);
+                scan_dir(udev_enumerate, subsysdir, "drivers", "drivers");
+        }
+        return 0;
+}
diff --git a/src/libudev-list.c b/src/libudev-list.c
new file mode 100644
index 0000000..4bdef35
--- /dev/null
+++ b/src/libudev-list.c
@@ -0,0 +1,344 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-list
+ * @short_description: list operation
+ *
+ * Libudev list operations.
+ */
+
+/**
+ * udev_list_entry:
+ *
+ * Opaque object representing one entry in a list. An entry contains
+ * contains a name, and optionally a value.
+ */
+struct udev_list_entry {
+        struct udev_list_node node;
+        struct udev_list *list;
+        char *name;
+        char *value;
+        int num;
+};
+
+/* the list's head points to itself if empty */
+void udev_list_node_init(struct udev_list_node *list)
+{
+        list->next = list;
+        list->prev = list;
+}
+
+int udev_list_node_is_empty(struct udev_list_node *list)
+{
+        return list->next == list;
+}
+
+static void udev_list_node_insert_between(struct udev_list_node *new,
+                                          struct udev_list_node *prev,
+                                          struct udev_list_node *next)
+{
+        next->prev = new;
+        new->next = next;
+        new->prev = prev;
+        prev->next = new;
+}
+
+void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list)
+{
+        udev_list_node_insert_between(new, list->prev, list);
+}
+
+void udev_list_node_remove(struct udev_list_node *entry)
+{
+        struct udev_list_node *prev = entry->prev;
+        struct udev_list_node *next = entry->next;
+
+        next->prev = prev;
+        prev->next = next;
+
+        entry->prev = NULL;
+        entry->next = NULL;
+}
+
+/* return list entry which embeds this node */
+static struct udev_list_entry *list_node_to_entry(struct udev_list_node *node)
+{
+        char *list;
+
+        list = (char *)node;
+        list -= offsetof(struct udev_list_entry, node);
+        return (struct udev_list_entry *)list;
+}
+
+void udev_list_init(struct udev *udev, struct udev_list *list, bool unique)
+{
+        memset(list, 0x00, sizeof(struct udev_list));
+        list->udev = udev;
+        list->unique = unique;
+        udev_list_node_init(&list->node);
+}
+
+/* insert entry into a list as the last element  */
+void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list)
+{
+        /* inserting before the list head make the node the last node in the list */
+        udev_list_node_insert_between(&new->node, list->node.prev, &list->node);
+        new->list = list;
+}
+
+/* insert entry into a list, before a given existing entry */
+void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry)
+{
+        udev_list_node_insert_between(&new->node, entry->node.prev, &entry->node);
+        new->list = entry->list;
+}
+
+/* binary search in sorted array */
+static int list_search(struct udev_list *list, const char *name)
+{
+        unsigned int first, last;
+
+        first = 0;
+        last = list->entries_cur;
+        while (first < last) {
+                unsigned int i;
+                int cmp;
+
+                i = (first + last)/2;
+                cmp = strcmp(name, list->entries[i]->name);
+                if (cmp < 0)
+                        last = i;
+                else if (cmp > 0)
+                        first = i+1;
+                else
+                        return i;
+        }
+
+        /* not found, return negative insertion-index+1 */
+        return -(first+1);
+}
+
+struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value)
+{
+        struct udev_list_entry *entry;
+        int i = 0;
+
+        if (list->unique) {
+                /* lookup existing name or insertion-index */
+                i = list_search(list, name);
+                if (i >= 0) {
+                        entry = list->entries[i];
+
+                        dbg(list->udev, "'%s' is already in the list\n", name);
+                        free(entry->value);
+                        if (value == NULL) {
+                                entry->value = NULL;
+                                dbg(list->udev, "'%s' value unset\n", name);
+                                return entry;
+                        }
+                        entry->value = strdup(value);
+                        if (entry->value == NULL)
+                                return NULL;
+                        dbg(list->udev, "'%s' value replaced with '%s'\n", name, value);
+                        return entry;
+                }
+        }
+
+        /* add new name */
+        entry = calloc(1, sizeof(struct udev_list_entry));
+        if (entry == NULL)
+                return NULL;
+        entry->name = strdup(name);
+        if (entry->name == NULL) {
+                free(entry);
+                return NULL;
+        }
+        if (value != NULL) {
+                entry->value = strdup(value);
+                if (entry->value == NULL) {
+                        free(entry->name);
+                        free(entry);
+                        return NULL;
+                }
+        }
+
+        if (list->unique) {
+                /* allocate or enlarge sorted array if needed */
+                if (list->entries_cur >= list->entries_max) {
+                        unsigned int add;
+
+                        add = list->entries_max;
+                        if (add < 1)
+                                add = 64;
+                        list->entries = realloc(list->entries, (list->entries_max + add) * sizeof(struct udev_list_entry *));
+                        if (list->entries == NULL) {
+                                free(entry->name);
+                                free(entry->value);
+                                return NULL;
+                        }
+                        list->entries_max += add;
+                }
+
+                /* the negative i returned the insertion index */
+                i = (-i)-1;
+
+                /* insert into sorted list */
+                if ((unsigned int)i < list->entries_cur)
+                        udev_list_entry_insert_before(entry, list->entries[i]);
+                else
+                        udev_list_entry_append(entry, list);
+
+                /* insert into sorted array */
+                memmove(&list->entries[i+1], &list->entries[i],
+                        (list->entries_cur - i) * sizeof(struct udev_list_entry *));
+                list->entries[i] = entry;
+                list->entries_cur++;
+        } else {
+                udev_list_entry_append(entry, list);
+        }
+
+        dbg(list->udev, "'%s=%s' added\n", entry->name, entry->value);
+        return entry;
+}
+
+void udev_list_entry_delete(struct udev_list_entry *entry)
+{
+        if (entry->list->entries != NULL) {
+                int i;
+                struct udev_list *list = entry->list;
+
+                /* remove entry from sorted array */
+                i = list_search(list, entry->name);
+                if (i >= 0) {
+                        memmove(&list->entries[i], &list->entries[i+1],
+                                ((list->entries_cur-1) - i) * sizeof(struct udev_list_entry *));
+                        list->entries_cur--;
+                }
+        }
+
+        udev_list_node_remove(&entry->node);
+        free(entry->name);
+        free(entry->value);
+        free(entry);
+}
+
+void udev_list_cleanup(struct udev_list *list)
+{
+        struct udev_list_entry *entry_loop;
+        struct udev_list_entry *entry_tmp;
+
+        free(list->entries);
+        list->entries = NULL;
+        list->entries_cur = 0;
+        list->entries_max = 0;
+        udev_list_entry_foreach_safe(entry_loop, entry_tmp, udev_list_get_entry(list))
+                udev_list_entry_delete(entry_loop);
+}
+
+struct udev_list_entry *udev_list_get_entry(struct udev_list *list)
+{
+        if (udev_list_node_is_empty(&list->node))
+                return NULL;
+        return list_node_to_entry(list->node.next);
+}
+
+/**
+ * udev_list_entry_get_next:
+ * @list_entry: current entry
+ *
+ * Returns: the next entry from the list, #NULL is no more entries are found.
+ */
+UDEV_EXPORT struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry)
+{
+        struct udev_list_node *next;
+
+        if (list_entry == NULL)
+                return NULL;
+        next = list_entry->node.next;
+        /* empty list or no more entries */
+        if (next == &list_entry->list->node)
+                return NULL;
+        return list_node_to_entry(next);
+}
+
+/**
+ * udev_list_entry_get_by_name:
+ * @list_entry: current entry
+ * @name: name string to match
+ *
+ * Returns: the entry where @name matched, #NULL if no matching entry is found.
+ */
+UDEV_EXPORT struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name)
+{
+        int i;
+
+        if (list_entry == NULL)
+                return NULL;
+
+        if (!list_entry->list->unique)
+                return NULL;
+
+        i = list_search(list_entry->list, name);
+        if (i < 0)
+                return NULL;
+        return list_entry->list->entries[i];
+}
+
+/**
+ * udev_list_entry_get_name:
+ * @list_entry: current entry
+ *
+ * Returns: the name string of this entry.
+ */
+UDEV_EXPORT const char *udev_list_entry_get_name(struct udev_list_entry *list_entry)
+{
+        if (list_entry == NULL)
+                return NULL;
+        return list_entry->name;
+}
+
+/**
+ * udev_list_entry_get_value:
+ * @list_entry: current entry
+ *
+ * Returns: the value string of this entry.
+ */
+UDEV_EXPORT const char *udev_list_entry_get_value(struct udev_list_entry *list_entry)
+{
+        if (list_entry == NULL)
+                return NULL;
+        return list_entry->value;
+}
+
+int udev_list_entry_get_num(struct udev_list_entry *list_entry)
+{
+        if (list_entry == NULL)
+                return -EINVAL;
+        return list_entry->num;
+}
+
+void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num)
+{
+        if (list_entry == NULL)
+                return;
+        list_entry->num = num;
+}
diff --git a/src/libudev-monitor.c b/src/libudev-monitor.c
new file mode 100644
index 0000000..77dc555
--- /dev/null
+++ b/src/libudev-monitor.c
@@ -0,0 +1,874 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/filter.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-monitor
+ * @short_description: device event source
+ *
+ * Connects to a device event source.
+ */
+
+/**
+ * udev_monitor:
+ *
+ * Opaque object handling an event source.
+ */
+struct udev_monitor {
+        struct udev *udev;
+        int refcount;
+        int sock;
+        struct sockaddr_nl snl;
+        struct sockaddr_nl snl_trusted_sender;
+        struct sockaddr_nl snl_destination;
+        struct sockaddr_un sun;
+        socklen_t addrlen;
+        struct udev_list filter_subsystem_list;
+        struct udev_list filter_tag_list;
+        bool bound;
+};
+
+enum udev_monitor_netlink_group {
+        UDEV_MONITOR_NONE,
+        UDEV_MONITOR_KERNEL,
+        UDEV_MONITOR_UDEV,
+};
+
+#define UDEV_MONITOR_MAGIC                0xfeedcafe
+struct udev_monitor_netlink_header {
+        /* "libudev" prefix to distinguish libudev and kernel messages */
+        char prefix[8];
+        /*
+         * magic to protect against daemon <-> library message format mismatch
+         * used in the kernel from socket filter rules; needs to be stored in network order
+         */
+        unsigned int magic;
+        /* total length of header structure known to the sender */
+        unsigned int header_size;
+        /* properties string buffer */
+        unsigned int properties_off;
+        unsigned int properties_len;
+        /*
+         * hashes of primary device properties strings, to let libudev subscribers
+         * use in-kernel socket filters; values need to be stored in network order
+         */
+        unsigned int filter_subsystem_hash;
+        unsigned int filter_devtype_hash;
+        unsigned int filter_tag_bloom_hi;
+        unsigned int filter_tag_bloom_lo;
+};
+
+static struct udev_monitor *udev_monitor_new(struct udev *udev)
+{
+        struct udev_monitor *udev_monitor;
+
+        udev_monitor = calloc(1, sizeof(struct udev_monitor));
+        if (udev_monitor == NULL)
+                return NULL;
+        udev_monitor->refcount = 1;
+        udev_monitor->udev = udev;
+        udev_list_init(udev, &udev_monitor->filter_subsystem_list, false);
+        udev_list_init(udev, &udev_monitor->filter_tag_list, true);
+        return udev_monitor;
+}
+
+/**
+ * udev_monitor_new_from_socket:
+ * @udev: udev library context
+ * @socket_path: unix socket path
+ *
+ * This function should not be used in any new application. The
+ * kernel's netlink socket multiplexes messages to all interested
+ * clients. Creating custom sockets from udev to applications
+ * should be avoided.
+ *
+ * Create a new udev monitor and connect to a specified socket. The
+ * path to a socket either points to an existing socket file, or if
+ * the socket path starts with a '@' character, an abstract namespace
+ * socket will be used.
+ *
+ * A socket file will not be created. If it does not already exist,
+ * it will fall-back and connect to an abstract namespace socket with
+ * the given path. The permissions adjustment of a socket file, as
+ * well as the later cleanup, needs to be done by the caller.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev monitor.
+ *
+ * Returns: a new udev monitor, or #NULL, in case of an error
+ **/
+UDEV_EXPORT struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path)
+{
+        struct udev_monitor *udev_monitor;
+        struct stat statbuf;
+
+        if (udev == NULL)
+                return NULL;
+        if (socket_path == NULL)
+                return NULL;
+        udev_monitor = udev_monitor_new(udev);
+        if (udev_monitor == NULL)
+                return NULL;
+
+        udev_monitor->sun.sun_family = AF_LOCAL;
+        if (socket_path[0] == '@') {
+                /* translate leading '@' to abstract namespace */
+                util_strscpy(udev_monitor->sun.sun_path, sizeof(udev_monitor->sun.sun_path), socket_path);
+                udev_monitor->sun.sun_path[0] = '\0';
+                udev_monitor->addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(socket_path);
+        } else if (stat(socket_path, &statbuf) == 0 && S_ISSOCK(statbuf.st_mode)) {
+                /* existing socket file */
+                util_strscpy(udev_monitor->sun.sun_path, sizeof(udev_monitor->sun.sun_path), socket_path);
+                udev_monitor->addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(socket_path);
+        } else {
+                /* no socket file, assume abstract namespace socket */
+                util_strscpy(&udev_monitor->sun.sun_path[1], sizeof(udev_monitor->sun.sun_path)-1, socket_path);
+                udev_monitor->addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(socket_path)+1;
+        }
+        udev_monitor->sock = socket(AF_LOCAL, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+        if (udev_monitor->sock == -1) {
+                err(udev, "error getting socket: %m\n");
+                free(udev_monitor);
+                return NULL;
+        }
+
+        dbg(udev, "monitor %p created with '%s'\n", udev_monitor, socket_path);
+        return udev_monitor;
+}
+
+struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd)
+{
+        struct udev_monitor *udev_monitor;
+        unsigned int group;
+
+        if (udev == NULL)
+                return NULL;
+
+        if (name == NULL)
+                group = UDEV_MONITOR_NONE;
+        else if (strcmp(name, "udev") == 0)
+                group = UDEV_MONITOR_UDEV;
+        else if (strcmp(name, "kernel") == 0)
+                group = UDEV_MONITOR_KERNEL;
+        else
+                return NULL;
+
+        udev_monitor = udev_monitor_new(udev);
+        if (udev_monitor == NULL)
+                return NULL;
+
+        if (fd < 0) {
+                udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT);
+                if (udev_monitor->sock == -1) {
+                        err(udev, "error getting socket: %m\n");
+                        free(udev_monitor);
+                        return NULL;
+                }
+        } else {
+                udev_monitor->bound = true;
+                udev_monitor->sock = fd;
+        }
+
+        udev_monitor->snl.nl_family = AF_NETLINK;
+        udev_monitor->snl.nl_groups = group;
+
+        /* default destination for sending */
+        udev_monitor->snl_destination.nl_family = AF_NETLINK;
+        udev_monitor->snl_destination.nl_groups = UDEV_MONITOR_UDEV;
+
+        dbg(udev, "monitor %p created with NETLINK_KOBJECT_UEVENT (%u)\n", udev_monitor, group);
+        return udev_monitor;
+}
+
+/**
+ * udev_monitor_new_from_netlink:
+ * @udev: udev library context
+ * @name: name of event source
+ *
+ * Create new udev monitor and connect to a specified event
+ * source. Valid sources identifiers are "udev" and "kernel".
+ *
+ * Applications should usually not connect directly to the
+ * "kernel" events, because the devices might not be useable
+ * at that time, before udev has configured them, and created
+ * device nodes. Accessing devices at the same time as udev,
+ * might result in unpredictable behavior. The "udev" events
+ * are sent out after udev has finished its event processing,
+ * all rules have been processed, and needed device nodes are
+ * created.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev monitor.
+ *
+ * Returns: a new udev monitor, or #NULL, in case of an error
+ **/
+UDEV_EXPORT struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
+{
+        return udev_monitor_new_from_netlink_fd(udev, name, -1);
+}
+
+static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i,
+                            unsigned short code, unsigned int data)
+{
+        struct sock_filter *ins = &inss[*i];
+
+        ins->code = code;
+        ins->k = data;
+        (*i)++;
+}
+
+static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i,
+                           unsigned short code, unsigned int data,
+                           unsigned short jt, unsigned short jf)
+{
+        struct sock_filter *ins = &inss[*i];
+
+        ins->code = code;
+        ins->jt = jt;
+        ins->jf = jf;
+        ins->k = data;
+        (*i)++;
+}
+
+/**
+ * udev_monitor_filter_update:
+ * @udev_monitor: monitor
+ *
+ * Update the installed socket filter. This is only needed,
+ * if the filter was removed or changed.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_monitor_filter_update(struct udev_monitor *udev_monitor)
+{
+        struct sock_filter ins[512];
+        struct sock_fprog filter;
+        unsigned int i;
+        struct udev_list_entry *list_entry;
+        int err;
+
+        if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL &&
+            udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL)
+                return 0;
+
+        memset(ins, 0x00, sizeof(ins));
+        i = 0;
+
+        /* load magic in A */
+        bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic));
+        /* jump if magic matches */
+        bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0);
+        /* wrong magic, pass packet */
+        bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
+
+        if (udev_list_get_entry(&udev_monitor->filter_tag_list) != NULL) {
+                int tag_matches;
+
+                /* count tag matches, to calculate end of tag match block */
+                tag_matches = 0;
+                udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list))
+                        tag_matches++;
+
+                /* add all tags matches */
+                udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) {
+                        uint64_t tag_bloom_bits = util_string_bloom64(udev_list_entry_get_name(list_entry));
+                        uint32_t tag_bloom_hi = tag_bloom_bits >> 32;
+                        uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff;
+
+                        /* load device bloom bits in A */
+                        bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_hi));
+                        /* clear bits (tag bits & bloom bits) */
+                        bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi);
+                        /* jump to next tag if it does not match */
+                        bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3);
+
+                        /* load device bloom bits in A */
+                        bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_lo));
+                        /* clear bits (tag bits & bloom bits) */
+                        bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo);
+                        /* jump behind end of tag match block if tag matches */
+                        tag_matches--;
+                        bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0);
+                }
+
+                /* nothing matched, drop packet */
+                bpf_stmt(ins, &i, BPF_RET|BPF_K, 0);
+        }
+
+        /* add all subsystem matches */
+        if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) != NULL) {
+                udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) {
+                        unsigned int hash = util_string_hash32(udev_list_entry_get_name(list_entry));
+
+                        /* load device subsystem value in A */
+                        bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem_hash));
+                        if (udev_list_entry_get_value(list_entry) == NULL) {
+                                /* jump if subsystem does not match */
+                                bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1);
+                        } else {
+                                /* jump if subsystem does not match */
+                                bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3);
+
+                                /* load device devtype value in A */
+                                bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype_hash));
+                                /* jump if value does not match */
+                                hash = util_string_hash32(udev_list_entry_get_value(list_entry));
+                                bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1);
+                        }
+
+                        /* matched, pass packet */
+                        bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
+
+                        if (i+1 >= ARRAY_SIZE(ins))
+                                return -1;
+                }
+
+                /* nothing matched, drop packet */
+                bpf_stmt(ins, &i, BPF_RET|BPF_K, 0);
+        }
+
+        /* matched, pass packet */
+        bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
+
+        /* install filter */
+        memset(&filter, 0x00, sizeof(filter));
+        filter.len = i;
+        filter.filter = ins;
+        err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
+        return err;
+}
+
+int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender)
+{
+        udev_monitor->snl_trusted_sender.nl_pid = sender->snl.nl_pid;
+        return 0;
+}
+/**
+ * udev_monitor_enable_receiving:
+ * @udev_monitor: the monitor which should receive events
+ *
+ * Binds the @udev_monitor socket to the event source.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
+{
+        int err = 0;
+        const int on = 1;
+
+        if (udev_monitor->sun.sun_family != 0) {
+                if (!udev_monitor->bound) {
+                        err = bind(udev_monitor->sock,
+                                   (struct sockaddr *)&udev_monitor->sun, udev_monitor->addrlen);
+                        if (err == 0)
+                                udev_monitor->bound = true;
+                }
+        } else if (udev_monitor->snl.nl_family != 0) {
+                udev_monitor_filter_update(udev_monitor);
+                if (!udev_monitor->bound) {
+                        err = bind(udev_monitor->sock,
+                                   (struct sockaddr *)&udev_monitor->snl, sizeof(struct sockaddr_nl));
+                        if (err == 0)
+                                udev_monitor->bound = true;
+                }
+                if (err == 0) {
+                        struct sockaddr_nl snl;
+                        socklen_t addrlen;
+
+                        /*
+                         * get the address the kernel has assigned us
+                         * it is usually, but not necessarily the pid
+                         */
+                        addrlen = sizeof(struct sockaddr_nl);
+                        err = getsockname(udev_monitor->sock, (struct sockaddr *)&snl, &addrlen);
+                        if (err == 0)
+                                udev_monitor->snl.nl_pid = snl.nl_pid;
+                }
+        } else {
+                return -EINVAL;
+        }
+
+        if (err < 0) {
+                err(udev_monitor->udev, "bind failed: %m\n");
+                return err;
+        }
+
+        /* enable receiving of sender credentials */
+        setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+        return 0;
+}
+
+/**
+ * udev_monitor_set_receive_buffer_size:
+ * @udev_monitor: the monitor which should receive events
+ * @size: the size in bytes
+ *
+ * Set the size of the kernel socket buffer. This call needs the
+ * appropriate privileges to succeed.
+ *
+ * Returns: 0 on success, otherwise -1 on error.
+ */
+UDEV_EXPORT int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size)
+{
+        if (udev_monitor == NULL)
+                return -1;
+        return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size));
+}
+
+int udev_monitor_disconnect(struct udev_monitor *udev_monitor)
+{
+        int err;
+
+        err = close(udev_monitor->sock);
+        udev_monitor->sock = -1;
+        return err;
+}
+
+/**
+ * udev_monitor_ref:
+ * @udev_monitor: udev monitor
+ *
+ * Take a reference of a udev monitor.
+ *
+ * Returns: the passed udev monitor
+ **/
+UDEV_EXPORT struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor)
+{
+        if (udev_monitor == NULL)
+                return NULL;
+        udev_monitor->refcount++;
+        return udev_monitor;
+}
+
+/**
+ * udev_monitor_unref:
+ * @udev_monitor: udev monitor
+ *
+ * Drop a reference of a udev monitor. If the refcount reaches zero,
+ * the bound socket will be closed, and the resources of the monitor
+ * will be released.
+ *
+ **/
+UDEV_EXPORT void udev_monitor_unref(struct udev_monitor *udev_monitor)
+{
+        if (udev_monitor == NULL)
+                return;
+        udev_monitor->refcount--;
+        if (udev_monitor->refcount > 0)
+                return;
+        if (udev_monitor->sock >= 0)
+                close(udev_monitor->sock);
+        udev_list_cleanup(&udev_monitor->filter_subsystem_list);
+        udev_list_cleanup(&udev_monitor->filter_tag_list);
+        dbg(udev_monitor->udev, "monitor %p released\n", udev_monitor);
+        free(udev_monitor);
+}
+
+/**
+ * udev_monitor_get_udev:
+ * @udev_monitor: udev monitor
+ *
+ * Retrieve the udev library context the monitor was created with.
+ *
+ * Returns: the udev library context
+ **/
+UDEV_EXPORT struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor)
+{
+        if (udev_monitor == NULL)
+                return NULL;
+        return udev_monitor->udev;
+}
+
+/**
+ * udev_monitor_get_fd:
+ * @udev_monitor: udev monitor
+ *
+ * Retrieve the socket file descriptor associated with the monitor.
+ *
+ * Returns: the socket file descriptor
+ **/
+UDEV_EXPORT int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
+{
+        if (udev_monitor == NULL)
+                return -1;
+        return udev_monitor->sock;
+}
+
+static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device *udev_device)
+{
+        struct udev_list_entry *list_entry;
+
+        if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL)
+                goto tag;
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) {
+                const char *subsys = udev_list_entry_get_name(list_entry);
+                const char *dsubsys = udev_device_get_subsystem(udev_device);
+                const char *devtype;
+                const char *ddevtype;
+
+                if (strcmp(dsubsys, subsys) != 0)
+                        continue;
+
+                devtype = udev_list_entry_get_value(list_entry);
+                if (devtype == NULL)
+                        goto tag;
+                ddevtype = udev_device_get_devtype(udev_device);
+                if (ddevtype == NULL)
+                        continue;
+                if (strcmp(ddevtype, devtype) == 0)
+                        goto tag;
+        }
+        return 0;
+
+tag:
+        if (udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL)
+                return 1;
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) {
+                const char *tag = udev_list_entry_get_name(list_entry);
+
+                if (udev_device_has_tag(udev_device, tag))
+                        return 1;
+        }
+        return 0;
+}
+
+/**
+ * udev_monitor_receive_device:
+ * @udev_monitor: udev monitor
+ *
+ * Receive data from the udev monitor socket, allocate a new udev
+ * device, fill in the received data, and return the device.
+ *
+ * Only socket connections with uid=0 are accepted.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, in case of an error
+ **/
+UDEV_EXPORT struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
+{
+        struct udev_device *udev_device;
+        struct msghdr smsg;
+        struct iovec iov;
+        char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+        struct cmsghdr *cmsg;
+        struct sockaddr_nl snl;
+        struct ucred *cred;
+        char buf[8192];
+        ssize_t buflen;
+        ssize_t bufpos;
+        struct udev_monitor_netlink_header *nlh;
+
+retry:
+        if (udev_monitor == NULL)
+                return NULL;
+        iov.iov_base = &buf;
+        iov.iov_len = sizeof(buf);
+        memset (&smsg, 0x00, sizeof(struct msghdr));
+        smsg.msg_iov = &iov;
+        smsg.msg_iovlen = 1;
+        smsg.msg_control = cred_msg;
+        smsg.msg_controllen = sizeof(cred_msg);
+
+        if (udev_monitor->snl.nl_family != 0) {
+                smsg.msg_name = &snl;
+                smsg.msg_namelen = sizeof(snl);
+        }
+
+        buflen = recvmsg(udev_monitor->sock, &smsg, 0);
+        if (buflen < 0) {
+                if (errno != EINTR)
+                        info(udev_monitor->udev, "unable to receive message\n");
+                return NULL;
+        }
+
+        if (buflen < 32 || (size_t)buflen >= sizeof(buf)) {
+                info(udev_monitor->udev, "invalid message length\n");
+                return NULL;
+        }
+
+        if (udev_monitor->snl.nl_family != 0) {
+                if (snl.nl_groups == 0) {
+                        /* unicast message, check if we trust the sender */
+                        if (udev_monitor->snl_trusted_sender.nl_pid == 0 ||
+                            snl.nl_pid != udev_monitor->snl_trusted_sender.nl_pid) {
+                                info(udev_monitor->udev, "unicast netlink message ignored\n");
+                                return NULL;
+                        }
+                } else if (snl.nl_groups == UDEV_MONITOR_KERNEL) {
+                        if (snl.nl_pid > 0) {
+                                info(udev_monitor->udev, "multicast kernel netlink message from pid %d ignored\n",
+                                     snl.nl_pid);
+                                return NULL;
+                        }
+                }
+        }
+
+        cmsg = CMSG_FIRSTHDR(&smsg);
+        if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+                info(udev_monitor->udev, "no sender credentials received, message ignored\n");
+                return NULL;
+        }
+
+        cred = (struct ucred *)CMSG_DATA(cmsg);
+        if (cred->uid != 0) {
+                info(udev_monitor->udev, "sender uid=%d, message ignored\n", cred->uid);
+                return NULL;
+        }
+
+        if (memcmp(buf, "libudev", 8) == 0) {
+                /* udev message needs proper version magic */
+                nlh = (struct udev_monitor_netlink_header *) buf;
+                if (nlh->magic != htonl(UDEV_MONITOR_MAGIC)) {
+                        err(udev_monitor->udev, "unrecognized message signature (%x != %x)\n",
+                            nlh->magic, htonl(UDEV_MONITOR_MAGIC));
+                        return NULL;
+                }
+                if (nlh->properties_off+32 > buflen)
+                        return NULL;
+                bufpos = nlh->properties_off;
+        } else {
+                /* kernel message with header */
+                bufpos = strlen(buf) + 1;
+                if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) {
+                        info(udev_monitor->udev, "invalid message length\n");
+                        return NULL;
+                }
+
+                /* check message header */
+                if (strstr(buf, "@/") == NULL) {
+                        info(udev_monitor->udev, "unrecognized message header\n");
+                        return NULL;
+                }
+        }
+
+        udev_device = udev_device_new(udev_monitor->udev);
+        if (udev_device == NULL)
+                return NULL;
+        udev_device_set_info_loaded(udev_device);
+
+        while (bufpos < buflen) {
+                char *key;
+                size_t keylen;
+
+                key = &buf[bufpos];
+                keylen = strlen(key);
+                if (keylen == 0)
+                        break;
+                bufpos += keylen + 1;
+                udev_device_add_property_from_string_parse(udev_device, key);
+        }
+
+        if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) {
+                info(udev_monitor->udev, "missing values, invalid device\n");
+                udev_device_unref(udev_device);
+                return NULL;
+        }
+
+        /* skip device, if it does not pass the current filter */
+        if (!passes_filter(udev_monitor, udev_device)) {
+                struct pollfd pfd[1];
+                int rc;
+
+                udev_device_unref(udev_device);
+
+                /* if something is queued, get next device */
+                pfd[0].fd = udev_monitor->sock;
+                pfd[0].events = POLLIN;
+                rc = poll(pfd, 1, 0);
+                if (rc > 0)
+                        goto retry;
+                return NULL;
+        }
+
+        return udev_device;
+}
+
+int udev_monitor_send_device(struct udev_monitor *udev_monitor,
+                             struct udev_monitor *destination, struct udev_device *udev_device)
+{
+        const char *buf;
+        ssize_t blen;
+        ssize_t count;
+
+        blen = udev_device_get_properties_monitor_buf(udev_device, &buf);
+        if (blen < 32)
+                return -EINVAL;
+
+        if (udev_monitor->sun.sun_family != 0) {
+                struct msghdr smsg;
+                struct iovec iov[2];
+                const char *action;
+                char header[2048];
+                char *s;
+
+                /* header <action>@<devpath> */
+                action = udev_device_get_action(udev_device);
+                if (action == NULL)
+                        return -EINVAL;
+                s = header;
+                if (util_strpcpyl(&s, sizeof(header), action, "@", udev_device_get_devpath(udev_device), NULL) == 0)
+                        return -EINVAL;
+                iov[0].iov_base = header;
+                iov[0].iov_len = (s - header)+1;
+
+                /* add properties list */
+                iov[1].iov_base = (char *)buf;
+                iov[1].iov_len = blen;
+
+                memset(&smsg, 0x00, sizeof(struct msghdr));
+                smsg.msg_iov = iov;
+                smsg.msg_iovlen = 2;
+                smsg.msg_name = &udev_monitor->sun;
+                smsg.msg_namelen = udev_monitor->addrlen;
+                count = sendmsg(udev_monitor->sock, &smsg, 0);
+                info(udev_monitor->udev, "passed %zi bytes to socket monitor %p\n", count, udev_monitor);
+                return count;
+        }
+
+        if (udev_monitor->snl.nl_family != 0) {
+                struct msghdr smsg;
+                struct iovec iov[2];
+                const char *val;
+                struct udev_monitor_netlink_header nlh;
+                struct udev_list_entry *list_entry;
+                uint64_t tag_bloom_bits;
+
+                /* add versioned header */
+                memset(&nlh, 0x00, sizeof(struct udev_monitor_netlink_header));
+                memcpy(nlh.prefix, "libudev", 8);
+                nlh.magic = htonl(UDEV_MONITOR_MAGIC);
+                nlh.header_size = sizeof(struct udev_monitor_netlink_header);
+                val = udev_device_get_subsystem(udev_device);
+                nlh.filter_subsystem_hash = htonl(util_string_hash32(val));
+                val = udev_device_get_devtype(udev_device);
+                if (val != NULL)
+                        nlh.filter_devtype_hash = htonl(util_string_hash32(val));
+                iov[0].iov_base = &nlh;
+                iov[0].iov_len = sizeof(struct udev_monitor_netlink_header);
+
+                /* add tag bloom filter */
+                tag_bloom_bits = 0;
+                udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device))
+                        tag_bloom_bits |= util_string_bloom64(udev_list_entry_get_name(list_entry));
+                if (tag_bloom_bits > 0) {
+                        nlh.filter_tag_bloom_hi = htonl(tag_bloom_bits >> 32);
+                        nlh.filter_tag_bloom_lo = htonl(tag_bloom_bits & 0xffffffff);
+                }
+
+                /* add properties list */
+                nlh.properties_off = iov[0].iov_len;
+                nlh.properties_len = blen;
+                iov[1].iov_base = (char *)buf;
+                iov[1].iov_len = blen;
+
+                memset(&smsg, 0x00, sizeof(struct msghdr));
+                smsg.msg_iov = iov;
+                smsg.msg_iovlen = 2;
+                /*
+                 * Use custom address for target, or the default one.
+                 *
+                 * If we send to a multicast group, we will get
+                 * ECONNREFUSED, which is expected.
+                 */
+                if (destination != NULL)
+                        smsg.msg_name = &destination->snl;
+                else
+                        smsg.msg_name = &udev_monitor->snl_destination;
+                smsg.msg_namelen = sizeof(struct sockaddr_nl);
+                count = sendmsg(udev_monitor->sock, &smsg, 0);
+                info(udev_monitor->udev, "passed %zi bytes to netlink monitor %p\n", count, udev_monitor);
+                return count;
+        }
+
+        return -EINVAL;
+}
+
+/**
+ * udev_monitor_filter_add_match_subsystem_devtype:
+ * @udev_monitor: the monitor
+ * @subsystem: the subsystem value to match the incoming devices against
+ * @devtype: the devtype value to match the incoming devices against
+ *
+ * This filter is efficiently executed inside the kernel, and libudev subscribers
+ * will usually not be woken up for devices which do not match.
+ *
+ * The filter must be installed before the monitor is switched to listening mode.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
+{
+        if (udev_monitor == NULL)
+                return -EINVAL;
+        if (subsystem == NULL)
+                return -EINVAL;
+        if (udev_list_entry_add(&udev_monitor->filter_subsystem_list, subsystem, devtype) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_monitor_filter_add_match_tag:
+ * @udev_monitor: the monitor
+ * @tag: the name of a tag
+ *
+ * This filter is efficiently executed inside the kernel, and libudev subscribers
+ * will usually not be woken up for devices which do not match.
+ *
+ * The filter must be installed before the monitor is switched to listening mode.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag)
+{
+        if (udev_monitor == NULL)
+                return -EINVAL;
+        if (tag == NULL)
+                return -EINVAL;
+        if (udev_list_entry_add(&udev_monitor->filter_tag_list, tag, NULL) == NULL)
+                return -ENOMEM;
+        return 0;
+}
+
+/**
+ * udev_monitor_filter_remove:
+ * @udev_monitor: monitor
+ *
+ * Remove all filters from monitor.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+UDEV_EXPORT int udev_monitor_filter_remove(struct udev_monitor *udev_monitor)
+{
+        static struct sock_fprog filter = { 0, NULL };
+
+        udev_list_cleanup(&udev_monitor->filter_subsystem_list);
+        return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
+}
diff --git a/src/libudev-private.h b/src/libudev-private.h
new file mode 100644
index 0000000..5f5c64a
--- /dev/null
+++ b/src/libudev-private.h
@@ -0,0 +1,213 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LIBUDEV_PRIVATE_H_
+#define _LIBUDEV_PRIVATE_H_
+
+#include <syslog.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "libudev.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define READ_END                                0
+#define WRITE_END                                1
+
+static inline void __attribute__((always_inline, format(printf, 2, 3)))
+udev_log_null(struct udev *udev, const char *format, ...) {}
+
+#define udev_log_cond(udev, prio, arg...) \
+  do { \
+    if (udev_get_log_priority(udev) >= prio) \
+      udev_log(udev, prio, __FILE__, __LINE__, __FUNCTION__, ## arg); \
+  } while (0)
+
+#ifdef ENABLE_LOGGING
+#  ifdef ENABLE_DEBUG
+#    define dbg(udev, arg...) udev_log_cond(udev, LOG_DEBUG, ## arg)
+#  else
+#    define dbg(udev, arg...) udev_log_null(udev, ## arg)
+#  endif
+#  define info(udev, arg...) udev_log_cond(udev, LOG_INFO, ## arg)
+#  define err(udev, arg...) udev_log_cond(udev, LOG_ERR, ## arg)
+#else
+#  define dbg(udev, arg...) udev_log_null(udev, ## arg)
+#  define info(udev, arg...) udev_log_null(udev, ## arg)
+#  define err(udev, arg...) udev_log_null(udev, ## arg)
+#endif
+
+#define UDEV_EXPORT __attribute__ ((visibility("default")))
+
+static inline void udev_log_init(const char *program_name)
+{
+        openlog(program_name, LOG_PID | LOG_CONS, LOG_DAEMON);
+}
+
+static inline void udev_log_close(void)
+{
+        closelog();
+}
+
+/* libudev.c */
+void udev_log(struct udev *udev,
+              int priority, const char *file, int line, const char *fn,
+              const char *format, ...)
+              __attribute__((format(printf, 6, 7)));
+int udev_get_rules_path(struct udev *udev, char **path[], unsigned long long *ts_usec[]);
+struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value);
+struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev);
+
+/* libudev-device.c */
+struct udev_device *udev_device_new(struct udev *udev);
+struct udev_device *udev_device_new_from_id_filename(struct udev *udev, char *id);
+mode_t udev_device_get_devnode_mode(struct udev_device *udev_device);
+int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath);
+int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode);
+int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink, int unique);
+void udev_device_cleanup_devlinks_list(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value);
+void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property);
+int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device);
+char **udev_device_get_properties_envp(struct udev_device *udev_device);
+ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf);
+int udev_device_read_db(struct udev_device *udev_device, const char *dbfile);
+int udev_device_read_uevent_file(struct udev_device *udev_device);
+int udev_device_set_action(struct udev_device *udev_device, const char *action);
+const char *udev_device_get_devpath_old(struct udev_device *udev_device);
+const char *udev_device_get_id_filename(struct udev_device *udev_device);
+void udev_device_set_is_initialized(struct udev_device *udev_device);
+int udev_device_add_tag(struct udev_device *udev_device, const char *tag);
+void udev_device_cleanup_tags_list(struct udev_device *udev_device);
+unsigned long long udev_device_get_usec_initialized(struct udev_device *udev_device);
+void udev_device_set_usec_initialized(struct udev_device *udev_device, unsigned long long usec_initialized);
+int udev_device_get_devlink_priority(struct udev_device *udev_device);
+int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio);
+int udev_device_get_watch_handle(struct udev_device *udev_device);
+int udev_device_set_watch_handle(struct udev_device *udev_device, int handle);
+int udev_device_get_ifindex(struct udev_device *udev_device);
+void udev_device_set_info_loaded(struct udev_device *device);
+bool udev_device_get_db_persist(struct udev_device *udev_device);
+void udev_device_set_db_persist(struct udev_device *udev_device);
+
+/* libudev-device-private.c */
+int udev_device_update_db(struct udev_device *udev_device);
+int udev_device_delete_db(struct udev_device *udev_device);
+int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add);
+
+/* libudev-monitor.c - netlink/unix socket communication  */
+int udev_monitor_disconnect(struct udev_monitor *udev_monitor);
+int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender);
+int udev_monitor_send_device(struct udev_monitor *udev_monitor,
+                             struct udev_monitor *destination, struct udev_device *udev_device);
+struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd);
+
+/* libudev-list.c */
+struct udev_list_node {
+        struct udev_list_node *next, *prev;
+};
+struct udev_list {
+        struct udev *udev;
+        struct udev_list_node node;
+        struct udev_list_entry **entries;
+        unsigned int entries_cur;
+        unsigned int entries_max;
+        bool unique;
+};
+#define UDEV_LIST(list) struct udev_list_node list = { &(list), &(list) }
+void udev_list_node_init(struct udev_list_node *list);
+int udev_list_node_is_empty(struct udev_list_node *list);
+void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list);
+void udev_list_node_remove(struct udev_list_node *entry);
+#define udev_list_node_foreach(node, list) \
+        for (node = (list)->next; \
+             node != list; \
+             node = (node)->next)
+#define udev_list_node_foreach_safe(node, tmp, list) \
+        for (node = (list)->next, tmp = (node)->next; \
+             node != list; \
+             node = tmp, tmp = (tmp)->next)
+void udev_list_init(struct udev *udev, struct udev_list *list, bool unique);
+void udev_list_cleanup(struct udev_list *list);
+struct udev_list_entry *udev_list_get_entry(struct udev_list *list);
+struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value);
+void udev_list_entry_delete(struct udev_list_entry *entry);
+void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry);
+void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list);
+int udev_list_entry_get_num(struct udev_list_entry *list_entry);
+void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num);
+#define udev_list_entry_foreach_safe(entry, tmp, first) \
+        for (entry = first, tmp = udev_list_entry_get_next(entry); \
+             entry != NULL; \
+             entry = tmp, tmp = udev_list_entry_get_next(tmp))
+
+/* libudev-queue.c */
+unsigned long long int udev_get_kernel_seqnum(struct udev *udev);
+int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum);
+ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size);
+ssize_t udev_queue_skip_devpath(FILE *queue_file);
+
+/* libudev-queue-private.c */
+struct udev_queue_export *udev_queue_export_new(struct udev *udev);
+struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export);
+void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export);
+int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device);
+int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device);
+
+/* libudev-util.c */
+#define UTIL_PATH_SIZE                                1024
+#define UTIL_NAME_SIZE                                512
+#define UTIL_LINE_SIZE                                16384
+#define UDEV_ALLOWED_CHARS_INPUT                "/ $%?,"
+ssize_t util_get_sys_core_link_value(struct udev *udev, const char *slink, const char *syspath, char *value, size_t size);
+int util_resolve_sys_link(struct udev *udev, char *syspath, size_t size);
+int util_log_priority(const char *priority);
+size_t util_path_encode(const char *src, char *dest, size_t size);
+size_t util_path_decode(char *s);
+void util_remove_trailing_chars(char *path, char c);
+size_t util_strpcpy(char **dest, size_t size, const char *src);
+size_t util_strpcpyl(char **dest, size_t size, const char *src, ...) __attribute__((sentinel));
+size_t util_strscpy(char *dest, size_t size, const char *src);
+size_t util_strscpyl(char *dest, size_t size, const char *src, ...) __attribute__((sentinel));
+int util_replace_whitespace(const char *str, char *to, size_t len);
+int util_replace_chars(char *str, const char *white);
+unsigned int util_string_hash32(const char *key);
+uint64_t util_string_bloom64(const char *str);
+
+/* libudev-util-private.c */
+int util_create_path(struct udev *udev, const char *path);
+int util_create_path_selinux(struct udev *udev, const char *path);
+int util_delete_path(struct udev *udev, const char *path);
+uid_t util_lookup_user(struct udev *udev, const char *user);
+gid_t util_lookup_group(struct udev *udev, const char *group);
+int util_resolve_subsys_kernel(struct udev *udev, const char *string,
+                                      char *result, size_t maxsize, int read_value);
+unsigned long long ts_usec(const struct timespec *ts);
+unsigned long long now_usec(void);
+
+/* libudev-selinux-private.c */
+#ifndef WITH_SELINUX
+static inline void udev_selinux_init(struct udev *udev) {}
+static inline void udev_selinux_exit(struct udev *udev) {}
+static inline void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode) {}
+static inline void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode) {}
+static inline void udev_selinux_setfscreateconat(struct udev *udev, int dfd, const char *file, unsigned int mode) {}
+static inline void udev_selinux_resetfscreatecon(struct udev *udev) {}
+#else
+void udev_selinux_init(struct udev *udev);
+void udev_selinux_exit(struct udev *udev);
+void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode);
+void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode);
+void udev_selinux_setfscreateconat(struct udev *udev, int dfd, const char *file, unsigned int mode);
+void udev_selinux_resetfscreatecon(struct udev *udev);
+#endif
+
+#endif
diff --git a/src/libudev-queue-private.c b/src/libudev-queue-private.c
new file mode 100644
index 0000000..7177195
--- /dev/null
+++ b/src/libudev-queue-private.c
@@ -0,0 +1,412 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+/*
+ * DISCLAIMER - The file format mentioned here is private to udev/libudev,
+ *              and may be changed without notice.
+ *
+ * The udev event queue is exported as a binary log file.
+ * Each log record consists of a sequence number followed by the device path.
+ *
+ * When a new event is queued, its details are appended to the log.
+ * When the event finishes, a second record is appended to the log
+ * with the same sequence number but a devpath len of 0.
+ *
+ * Example:
+ *        { 0x0000000000000001 }
+ *        { 0x0000000000000001, 0x0019, "/devices/virtual/mem/null" },
+ *        { 0x0000000000000002, 0x001b, "/devices/virtual/mem/random" },
+ *        { 0x0000000000000001, 0x0000 },
+ *        { 0x0000000000000003, 0x0019, "/devices/virtual/mem/zero" },
+ *
+ * Events 2 and 3 are still queued, but event 1 has finished.
+ *
+ * The queue does not grow indefinitely. It is periodically re-created
+ * to remove finished events. Atomic rename() makes this transparent to readers.
+ *
+ * The queue file starts with a single sequence number which specifies the
+ * minimum sequence number in the log that follows. Any events prior to this
+ * sequence number have already finished.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static int rebuild_queue_file(struct udev_queue_export *udev_queue_export);
+
+struct udev_queue_export {
+        struct udev *udev;
+        int queued_count;        /* number of unfinished events exported in queue file */
+        FILE *queue_file;
+        unsigned long long int seqnum_max;        /* earliest sequence number in queue file */
+        unsigned long long int seqnum_min;        /* latest sequence number in queue file */
+        int waste_bytes;                        /* queue file bytes wasted on finished events */
+};
+
+struct udev_queue_export *udev_queue_export_new(struct udev *udev)
+{
+        struct udev_queue_export *udev_queue_export;
+        unsigned long long int initial_seqnum;
+
+        if (udev == NULL)
+                return NULL;
+
+        udev_queue_export = calloc(1, sizeof(struct udev_queue_export));
+        if (udev_queue_export == NULL)
+                return NULL;
+        udev_queue_export->udev = udev;
+
+        initial_seqnum = udev_get_kernel_seqnum(udev);
+        udev_queue_export->seqnum_min = initial_seqnum;
+        udev_queue_export->seqnum_max = initial_seqnum;
+
+        udev_queue_export_cleanup(udev_queue_export);
+        if (rebuild_queue_file(udev_queue_export) != 0) {
+                free(udev_queue_export);
+                return NULL;
+        }
+
+        return udev_queue_export;
+}
+
+struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export)
+{
+        if (udev_queue_export == NULL)
+                return NULL;
+        if (udev_queue_export->queue_file != NULL)
+                fclose(udev_queue_export->queue_file);
+        free(udev_queue_export);
+        return NULL;
+}
+
+void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export)
+{
+        char filename[UTIL_PATH_SIZE];
+
+        if (udev_queue_export == NULL)
+                return;
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_queue_export->udev), "/queue.tmp", NULL);
+        unlink(filename);
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_queue_export->udev), "/queue.bin", NULL);
+        unlink(filename);
+}
+
+static int skip_to(FILE *file, long offset)
+{
+        long old_offset;
+
+        /* fseek may drop buffered data, avoid it for small seeks */
+        old_offset = ftell(file);
+        if (offset > old_offset && offset - old_offset <= BUFSIZ) {
+                size_t skip_bytes = offset - old_offset;
+                char buf[skip_bytes];
+
+                if (fread(buf, skip_bytes, 1, file) != skip_bytes)
+                        return -1;
+        }
+
+        return fseek(file, offset, SEEK_SET);
+}
+
+struct queue_devpaths {
+        unsigned int devpaths_first;        /* index of first queued event */
+        unsigned int devpaths_size;
+        long devpaths[];                /* seqnum -> offset of devpath in queue file (or 0) */
+};
+
+/*
+ * Returns a table mapping seqnum to devpath file offset for currently queued events.
+ * devpaths[i] represents the event with seqnum = i + udev_queue_export->seqnum_min.
+ */
+static struct queue_devpaths *build_index(struct udev_queue_export *udev_queue_export)
+{
+        struct queue_devpaths *devpaths;
+        unsigned long long int range;
+        long devpath_offset;
+        ssize_t devpath_len;
+        unsigned long long int seqnum;
+        unsigned long long int n;
+        unsigned int i;
+
+        /* seek to the first event in the file */
+        rewind(udev_queue_export->queue_file);
+        udev_queue_read_seqnum(udev_queue_export->queue_file, &seqnum);
+
+        /* allocate the table */
+        range = udev_queue_export->seqnum_min - udev_queue_export->seqnum_max;
+        if (range - 1 > INT_MAX) {
+                err(udev_queue_export->udev, "queue file overflow\n");
+                return NULL;
+        }
+        devpaths = calloc(1, sizeof(struct queue_devpaths) + (range + 1) * sizeof(long));
+        if (devpaths == NULL)
+                return NULL;
+        devpaths->devpaths_size = range + 1;
+
+        /* read all records and populate the table */
+        for (;;) {
+                if (udev_queue_read_seqnum(udev_queue_export->queue_file, &seqnum) < 0)
+                        break;
+                n = seqnum - udev_queue_export->seqnum_max;
+                if (n >= devpaths->devpaths_size)
+                        goto read_error;
+
+                devpath_offset = ftell(udev_queue_export->queue_file);
+                devpath_len = udev_queue_skip_devpath(udev_queue_export->queue_file);
+                if (devpath_len < 0)
+                        goto read_error;
+
+                if (devpath_len > 0)
+                        devpaths->devpaths[n] = devpath_offset;
+                else
+                        devpaths->devpaths[n] = 0;
+        }
+
+        /* find first queued event */
+        for (i = 0; i < devpaths->devpaths_size; i++) {
+                if (devpaths->devpaths[i] != 0)
+                        break;
+        }
+        devpaths->devpaths_first = i;
+
+        return devpaths;
+
+read_error:
+        err(udev_queue_export->udev, "queue file corrupted\n");
+        free(devpaths);
+        return NULL;
+}
+
+static int rebuild_queue_file(struct udev_queue_export *udev_queue_export)
+{
+        unsigned long long int seqnum;
+        struct queue_devpaths *devpaths = NULL;
+        char filename[UTIL_PATH_SIZE];
+        char filename_tmp[UTIL_PATH_SIZE];
+        FILE *new_queue_file = NULL;
+        unsigned int i;
+
+        /* read old queue file */
+        if (udev_queue_export->queue_file != NULL) {
+                dbg(udev_queue_export->udev, "compacting queue file, freeing %d bytes\n",
+                                                udev_queue_export->waste_bytes);
+
+                devpaths = build_index(udev_queue_export);
+                if (devpaths != NULL)
+                        udev_queue_export->seqnum_max += devpaths->devpaths_first;
+        }
+        if (devpaths == NULL) {
+                dbg(udev_queue_export->udev, "creating empty queue file\n");
+                udev_queue_export->queued_count = 0;
+                udev_queue_export->seqnum_max = udev_queue_export->seqnum_min;
+        }
+
+        /* create new queue file */
+        util_strscpyl(filename_tmp, sizeof(filename_tmp), udev_get_run_path(udev_queue_export->udev), "/queue.tmp", NULL);
+        new_queue_file = fopen(filename_tmp, "w+");
+        if (new_queue_file == NULL)
+                goto error;
+        seqnum = udev_queue_export->seqnum_max;
+        fwrite(&seqnum, 1, sizeof(unsigned long long int), new_queue_file);
+
+        /* copy unfinished events only to the new file */
+        if (devpaths != NULL) {
+                for (i = devpaths->devpaths_first; i < devpaths->devpaths_size; i++) {
+                        char devpath[UTIL_PATH_SIZE];
+                        int err;
+                        unsigned short devpath_len;
+
+                        if (devpaths->devpaths[i] != 0)
+                        {
+                                skip_to(udev_queue_export->queue_file, devpaths->devpaths[i]);
+                                err = udev_queue_read_devpath(udev_queue_export->queue_file, devpath, sizeof(devpath));
+                                devpath_len = err;
+
+                                fwrite(&seqnum, sizeof(unsigned long long int), 1, new_queue_file);
+                                fwrite(&devpath_len, sizeof(unsigned short), 1, new_queue_file);
+                                fwrite(devpath, 1, devpath_len, new_queue_file);
+                        }
+                        seqnum++;
+                }
+                free(devpaths);
+                devpaths = NULL;
+        }
+        fflush(new_queue_file);
+        if (ferror(new_queue_file))
+                goto error;
+
+        /* rename the new file on top of the old one */
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_queue_export->udev), "/queue.bin", NULL);
+        if (rename(filename_tmp, filename) != 0)
+                goto error;
+
+        if (udev_queue_export->queue_file != NULL)
+                fclose(udev_queue_export->queue_file);
+        udev_queue_export->queue_file = new_queue_file;
+        udev_queue_export->waste_bytes = 0;
+
+        return 0;
+
+error:
+        err(udev_queue_export->udev, "failed to create queue file: %m\n");
+        udev_queue_export_cleanup(udev_queue_export);
+
+        if (udev_queue_export->queue_file != NULL) {
+                fclose(udev_queue_export->queue_file);
+                udev_queue_export->queue_file = NULL;
+        }
+        if (new_queue_file != NULL)
+                fclose(new_queue_file);
+
+        if (devpaths != NULL)
+                free(devpaths);
+        udev_queue_export->queued_count = 0;
+        udev_queue_export->waste_bytes = 0;
+        udev_queue_export->seqnum_max = udev_queue_export->seqnum_min;
+
+        return -1;
+}
+
+static int write_queue_record(struct udev_queue_export *udev_queue_export,
+                              unsigned long long int seqnum, const char *devpath, size_t devpath_len)
+{
+        unsigned short len;
+
+        if (udev_queue_export->queue_file == NULL) {
+                dbg(udev_queue_export->udev, "can't record event: queue file not available\n");
+                return -1;
+        }
+
+        if (fwrite(&seqnum, sizeof(unsigned long long int), 1, udev_queue_export->queue_file) != 1)
+                goto write_error;
+
+        len = (devpath_len < USHRT_MAX) ? devpath_len : USHRT_MAX;
+        if (fwrite(&len, sizeof(unsigned short), 1, udev_queue_export->queue_file) != 1)
+                goto write_error;
+        if (len > 0) {
+                if (fwrite(devpath, 1, len, udev_queue_export->queue_file) != len)
+                        goto write_error;
+        }
+
+        /* *must* flush output; caller may fork */
+        if (fflush(udev_queue_export->queue_file) != 0)
+                goto write_error;
+
+        return 0;
+
+write_error:
+        /* if we failed half way through writing a record to a file,
+           we should not try to write any further records to it. */
+        err(udev_queue_export->udev, "error writing to queue file: %m\n");
+        fclose(udev_queue_export->queue_file);
+        udev_queue_export->queue_file = NULL;
+
+        return -1;
+}
+
+enum device_state {
+        DEVICE_QUEUED,
+        DEVICE_FINISHED,
+};
+
+static inline size_t queue_record_size(size_t devpath_len)
+{
+        return sizeof(unsigned long long int) + sizeof(unsigned short int) + devpath_len;
+}
+
+static int update_queue(struct udev_queue_export *udev_queue_export,
+                         struct udev_device *udev_device, enum device_state state)
+{
+        unsigned long long int seqnum = udev_device_get_seqnum(udev_device);
+        const char *devpath = NULL;
+        size_t devpath_len = 0;
+        int bytes;
+        int err;
+
+        /* FINISHED records have a zero length devpath */
+        if (state == DEVICE_QUEUED) {
+                devpath = udev_device_get_devpath(udev_device);
+                devpath_len = strlen(devpath);
+        }
+
+        /* recover from an earlier failed rebuild */
+        if (udev_queue_export->queue_file == NULL) {
+                if (rebuild_queue_file(udev_queue_export) != 0)
+                        return -1;
+        }
+
+        /* if we're removing the last event from the queue, that's the best time to rebuild it */
+        if (state != DEVICE_QUEUED && udev_queue_export->queued_count == 1) {
+                /* we don't need to read the old queue file */
+                fclose(udev_queue_export->queue_file);
+                udev_queue_export->queue_file = NULL;
+                rebuild_queue_file(udev_queue_export);
+                return 0;
+        }
+
+        /* try to rebuild the queue files before they grow larger than one page. */
+        bytes = ftell(udev_queue_export->queue_file) + queue_record_size(devpath_len);
+        if ((udev_queue_export->waste_bytes > bytes / 2) && bytes > 4096)
+                rebuild_queue_file(udev_queue_export);
+
+        /* don't record a finished event, if we already dropped the event in a failed rebuild */
+        if (seqnum < udev_queue_export->seqnum_max)
+                return 0;
+
+        /* now write to the queue */
+        if (state == DEVICE_QUEUED) {
+                udev_queue_export->queued_count++;
+                udev_queue_export->seqnum_min = seqnum;
+        } else {
+                udev_queue_export->waste_bytes += queue_record_size(devpath_len) + queue_record_size(0);
+                udev_queue_export->queued_count--;
+        }
+        err = write_queue_record(udev_queue_export, seqnum, devpath, devpath_len);
+
+        /* try to handle ENOSPC */
+        if (err != 0 && udev_queue_export->queued_count == 0) {
+                udev_queue_export_cleanup(udev_queue_export);
+                err = rebuild_queue_file(udev_queue_export);
+        }
+
+        return err;
+}
+
+static int update(struct udev_queue_export *udev_queue_export,
+                  struct udev_device *udev_device, enum device_state state)
+{
+        if (update_queue(udev_queue_export, udev_device, state) != 0)
+                return -1;
+
+        return 0;
+}
+
+int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device)
+{
+        return update(udev_queue_export, udev_device, DEVICE_QUEUED);
+}
+
+int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device)
+{
+        return update(udev_queue_export, udev_device, DEVICE_FINISHED);
+}
diff --git a/src/libudev-queue.c b/src/libudev-queue.c
new file mode 100644
index 0000000..0e82cb6
--- /dev/null
+++ b/src/libudev-queue.c
@@ -0,0 +1,474 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-queue
+ * @short_description: access to currently active events
+ *
+ * The udev daemon processes events asynchronously. All events which do not have
+ * interdependencies run in parallel. This exports the current state of the
+ * event processing queue, and the current event sequence numbers from the kernel
+ * and the udev daemon.
+ */
+
+/**
+ * udev_queue:
+ *
+ * Opaque object representing the current event queue in the udev daemon.
+ */
+struct udev_queue {
+        struct udev *udev;
+        int refcount;
+        struct udev_list queue_list;
+};
+
+/**
+ * udev_queue_new:
+ * @udev: udev library context
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev queue context.
+ *
+ * Returns: the udev queue context, or #NULL on error.
+ **/
+UDEV_EXPORT struct udev_queue *udev_queue_new(struct udev *udev)
+{
+        struct udev_queue *udev_queue;
+
+        if (udev == NULL)
+                return NULL;
+
+        udev_queue = calloc(1, sizeof(struct udev_queue));
+        if (udev_queue == NULL)
+                return NULL;
+        udev_queue->refcount = 1;
+        udev_queue->udev = udev;
+        udev_list_init(udev, &udev_queue->queue_list, false);
+        return udev_queue;
+}
+
+/**
+ * udev_queue_ref:
+ * @udev_queue: udev queue context
+ *
+ * Take a reference of a udev queue context.
+ *
+ * Returns: the same udev queue context.
+ **/
+UDEV_EXPORT struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue)
+{
+        if (udev_queue == NULL)
+                return NULL;
+        udev_queue->refcount++;
+        return udev_queue;
+}
+
+/**
+ * udev_queue_unref:
+ * @udev_queue: udev queue context
+ *
+ * Drop a reference of a udev queue context. If the refcount reaches zero,
+ * the resources of the queue context will be released.
+ **/
+UDEV_EXPORT void udev_queue_unref(struct udev_queue *udev_queue)
+{
+        if (udev_queue == NULL)
+                return;
+        udev_queue->refcount--;
+        if (udev_queue->refcount > 0)
+                return;
+        udev_list_cleanup(&udev_queue->queue_list);
+        free(udev_queue);
+}
+
+/**
+ * udev_queue_get_udev:
+ * @udev_queue: udev queue context
+ *
+ * Retrieve the udev library context the queue context was created with.
+ *
+ * Returns: the udev library context.
+ **/
+UDEV_EXPORT struct udev *udev_queue_get_udev(struct udev_queue *udev_queue)
+{
+        if (udev_queue == NULL)
+                return NULL;
+        return udev_queue->udev;
+}
+
+unsigned long long int udev_get_kernel_seqnum(struct udev *udev)
+{
+        char filename[UTIL_PATH_SIZE];
+        unsigned long long int seqnum;
+        int fd;
+        char buf[32];
+        ssize_t len;
+
+        util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), "/kernel/uevent_seqnum", NULL);
+        fd = open(filename, O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return 0;
+        len = read(fd, buf, sizeof(buf));
+        close(fd);
+        if (len <= 2)
+                return 0;
+        buf[len-1] = '\0';
+        seqnum = strtoull(buf, NULL, 10);
+        return seqnum;
+}
+
+/**
+ * udev_queue_get_kernel_seqnum:
+ * @udev_queue: udev queue context
+ *
+ * Returns: the current kernel event sequence number.
+ **/
+UDEV_EXPORT unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue)
+{
+        unsigned long long int seqnum;
+
+        if (udev_queue == NULL)
+                return -EINVAL;
+
+        seqnum = udev_get_kernel_seqnum(udev_queue->udev);
+        dbg(udev_queue->udev, "seqnum=%llu\n", seqnum);
+        return seqnum;
+}
+
+int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum)
+{
+        if (fread(seqnum, sizeof(unsigned long long int), 1, queue_file) != 1)
+                return -1;
+
+        return 0;
+}
+
+ssize_t udev_queue_skip_devpath(FILE *queue_file)
+{
+        unsigned short int len;
+
+        if (fread(&len, sizeof(unsigned short int), 1, queue_file) == 1) {
+                char devpath[len];
+
+                /* use fread to skip, fseek might drop buffered data */
+                if (fread(devpath, 1, len, queue_file) == len)
+                        return len;
+        }
+
+        return -1;
+}
+
+ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size)
+{
+        unsigned short int read_bytes = 0;
+        unsigned short int len;
+
+        if (fread(&len, sizeof(unsigned short int), 1, queue_file) != 1)
+                return -1;
+
+        read_bytes = (len < size - 1) ? len : size - 1;
+        if (fread(devpath, 1, read_bytes, queue_file) != read_bytes)
+                return -1;
+        devpath[read_bytes] = '\0';
+
+        /* if devpath was too long, skip unread characters */
+        if (read_bytes != len) {
+                unsigned short int skip_bytes = len - read_bytes;
+                char buf[skip_bytes];
+
+                if (fread(buf, 1, skip_bytes, queue_file) != skip_bytes)
+                        return -1;
+        }
+
+        return read_bytes;
+}
+
+static FILE *open_queue_file(struct udev_queue *udev_queue, unsigned long long int *seqnum_start)
+{
+        char filename[UTIL_PATH_SIZE];
+        FILE *queue_file;
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_queue->udev), "/queue.bin", NULL);
+        queue_file = fopen(filename, "re");
+        if (queue_file == NULL)
+                return NULL;
+
+        if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) {
+                err(udev_queue->udev, "corrupt queue file\n");
+                fclose(queue_file);
+                return NULL;
+        }
+
+        return queue_file;
+}
+
+/**
+ * udev_queue_get_udev_seqnum:
+ * @udev_queue: udev queue context
+ *
+ * Returns: the last known udev event sequence number.
+ **/
+UDEV_EXPORT unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue)
+{
+        unsigned long long int seqnum_udev;
+        FILE *queue_file;
+
+        queue_file = open_queue_file(udev_queue, &seqnum_udev);
+        if (queue_file == NULL)
+                return 0;
+
+        for (;;) {
+                unsigned long long int seqnum;
+                ssize_t devpath_len;
+
+                if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+                        break;
+                devpath_len = udev_queue_skip_devpath(queue_file);
+                if (devpath_len < 0)
+                        break;
+                if (devpath_len > 0)
+                        seqnum_udev = seqnum;
+        }
+
+        fclose(queue_file);
+        return seqnum_udev;
+}
+
+/**
+ * udev_queue_get_udev_is_active:
+ * @udev_queue: udev queue context
+ *
+ * Returns: a flag indicating if udev is active.
+ **/
+UDEV_EXPORT int udev_queue_get_udev_is_active(struct udev_queue *udev_queue)
+{
+        unsigned long long int seqnum_start;
+        FILE *queue_file;
+
+        queue_file = open_queue_file(udev_queue, &seqnum_start);
+        if (queue_file == NULL)
+                return 0;
+
+        fclose(queue_file);
+        return 1;
+}
+
+/**
+ * udev_queue_get_queue_is_empty:
+ * @udev_queue: udev queue context
+ *
+ * Returns: a flag indicating if udev is currently handling events.
+ **/
+UDEV_EXPORT int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue)
+{
+        unsigned long long int seqnum_kernel;
+        unsigned long long int seqnum_udev = 0;
+        int queued = 0;
+        int is_empty = 0;
+        FILE *queue_file;
+
+        if (udev_queue == NULL)
+                return -EINVAL;
+        queue_file = open_queue_file(udev_queue, &seqnum_udev);
+        if (queue_file == NULL)
+                return 1;
+
+        for (;;) {
+                unsigned long long int seqnum;
+                ssize_t devpath_len;
+
+                if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+                        break;
+                devpath_len = udev_queue_skip_devpath(queue_file);
+                if (devpath_len < 0)
+                        break;
+
+                if (devpath_len > 0) {
+                        queued++;
+                        seqnum_udev = seqnum;
+                } else {
+                        queued--;
+                }
+        }
+
+        if (queued > 0) {
+                dbg(udev_queue->udev, "queue is not empty\n");
+                goto out;
+        }
+
+        seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue);
+        if (seqnum_udev < seqnum_kernel) {
+                dbg(udev_queue->udev, "queue is empty but kernel events still pending [%llu]<->[%llu]\n",
+                                      seqnum_kernel, seqnum_udev);
+                goto out;
+        }
+
+        dbg(udev_queue->udev, "queue is empty\n");
+        is_empty = 1;
+
+out:
+        fclose(queue_file);
+        return is_empty;
+}
+
+/**
+ * udev_queue_get_seqnum_sequence_is_finished:
+ * @udev_queue: udev queue context
+ * @start: first event sequence number
+ * @end: last event sequence number
+ *
+ * Returns: a flag indicating if any of the sequence numbers in the given range is currently active.
+ **/
+UDEV_EXPORT int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+                                               unsigned long long int start, unsigned long long int end)
+{
+        unsigned long long int seqnum;
+        ssize_t devpath_len;
+        int unfinished;
+        FILE *queue_file;
+
+        if (udev_queue == NULL)
+                return -EINVAL;
+        queue_file = open_queue_file(udev_queue, &seqnum);
+        if (queue_file == NULL)
+                return 1;
+        if (start < seqnum)
+                start = seqnum;
+        if (start > end) {
+                fclose(queue_file);
+                return 1;
+        }
+        if (end - start > INT_MAX - 1) {
+                fclose(queue_file);
+                return -EOVERFLOW;
+        }
+
+        /*
+         * we might start with 0, and handle the initial seqnum
+         * only when we find an entry in the queue file
+         **/
+        unfinished = end - start;
+
+        do {
+                if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+                        break;
+                devpath_len = udev_queue_skip_devpath(queue_file);
+                if (devpath_len < 0)
+                        break;
+
+                /*
+                 * we might start with an empty or re-build queue file, where
+                 * the initial seqnum is not recorded as finished
+                 */
+                if (start == seqnum && devpath_len > 0)
+                        unfinished++;
+
+                if (devpath_len == 0) {
+                        if (seqnum >= start && seqnum <= end)
+                                unfinished--;
+                }
+        } while (unfinished > 0);
+
+        fclose(queue_file);
+
+        return (unfinished == 0);
+}
+
+/**
+ * udev_queue_get_seqnum_is_finished:
+ * @udev_queue: udev queue context
+ * @seqnum: sequence number
+ *
+ * Returns: a flag indicating if the given sequence number is currently active.
+ **/
+UDEV_EXPORT int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum)
+{
+        if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum))
+                return 0;
+
+        dbg(udev_queue->udev, "seqnum: %llu finished\n", seqnum);
+        return 1;
+}
+
+/**
+ * udev_queue_get_queued_list_entry:
+ * @udev_queue: udev queue context
+ *
+ * Returns: the first entry of the list of queued events.
+ **/
+UDEV_EXPORT struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue)
+{
+        unsigned long long int seqnum;
+        FILE *queue_file;
+
+        if (udev_queue == NULL)
+                return NULL;
+        udev_list_cleanup(&udev_queue->queue_list);
+
+        queue_file = open_queue_file(udev_queue, &seqnum);
+        if (queue_file == NULL)
+                return NULL;
+
+        for (;;) {
+                char syspath[UTIL_PATH_SIZE];
+                char *s;
+                size_t l;
+                ssize_t len;
+                char seqnum_str[32];
+                struct udev_list_entry *list_entry;
+
+                if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+                        break;
+                snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum);
+
+                s = syspath;
+                l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL);
+                len = udev_queue_read_devpath(queue_file, s, l);
+                if (len < 0)
+                        break;
+
+                if (len > 0) {
+                        udev_list_entry_add(&udev_queue->queue_list, syspath, seqnum_str);
+                } else {
+                        udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) {
+                                if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) {
+                                        udev_list_entry_delete(list_entry);
+                                        break;
+                                }
+                        }
+                }
+        }
+        fclose(queue_file);
+
+        return udev_list_get_entry(&udev_queue->queue_list);
+}
+
+struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue);
+UDEV_EXPORT struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue)
+{
+        errno = ENOSYS;
+        return NULL;
+}
diff --git a/src/libudev-selinux-private.c b/src/libudev-selinux-private.c
new file mode 100644
index 0000000..0f2a617
--- /dev/null
+++ b/src/libudev-selinux-private.c
@@ -0,0 +1,109 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <selinux/selinux.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static int selinux_enabled;
+security_context_t selinux_prev_scontext;
+
+void udev_selinux_init(struct udev *udev)
+{
+        /* record the present security context */
+        selinux_enabled = (is_selinux_enabled() > 0);
+        info(udev, "selinux=%i\n", selinux_enabled);
+        if (!selinux_enabled)
+                return;
+        matchpathcon_init_prefix(NULL, udev_get_dev_path(udev));
+        if (getfscreatecon(&selinux_prev_scontext) < 0) {
+                err(udev, "getfscreatecon failed\n");
+                selinux_prev_scontext = NULL;
+        }
+}
+
+void udev_selinux_exit(struct udev *udev)
+{
+        if (!selinux_enabled)
+                return;
+        freecon(selinux_prev_scontext);
+        selinux_prev_scontext = NULL;
+}
+
+void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode)
+{
+        security_context_t scontext = NULL;
+
+        if (!selinux_enabled)
+                return;
+        if (matchpathcon(file, mode, &scontext) < 0) {
+                err(udev, "matchpathcon(%s) failed\n", file);
+                return;
+        }
+        if (lsetfilecon(file, scontext) < 0)
+                err(udev, "setfilecon %s failed: %m\n", file);
+        freecon(scontext);
+}
+
+void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode)
+{
+        security_context_t scontext = NULL;
+
+        if (!selinux_enabled)
+                return;
+
+        if (matchpathcon(file, mode, &scontext) < 0) {
+                err(udev, "matchpathcon(%s) failed\n", file);
+                return;
+        }
+        if (setfscreatecon(scontext) < 0)
+                err(udev, "setfscreatecon %s failed: %m\n", file);
+        freecon(scontext);
+}
+
+void udev_selinux_resetfscreatecon(struct udev *udev)
+{
+        if (!selinux_enabled)
+                return;
+        if (setfscreatecon(selinux_prev_scontext) < 0)
+                err(udev, "setfscreatecon failed: %m\n");
+}
+
+void udev_selinux_setfscreateconat(struct udev *udev, int dfd, const char *file, unsigned int mode)
+{
+        char filename[UTIL_PATH_SIZE];
+
+        if (!selinux_enabled)
+                return;
+
+        /* resolve relative filename */
+        if (file[0] != '/') {
+                char procfd[UTIL_PATH_SIZE];
+                char target[UTIL_PATH_SIZE];
+                ssize_t len;
+
+                snprintf(procfd, sizeof(procfd), "/proc/%u/fd/%u", getpid(), dfd);
+                len = readlink(procfd, target, sizeof(target));
+                if (len <= 0 || len == sizeof(target))
+                        return;
+                target[len] = '\0';
+
+                util_strscpyl(filename, sizeof(filename), target, "/", file, NULL);
+                file = filename;
+        }
+        udev_selinux_setfscreatecon(udev, file, mode);
+}
diff --git a/src/libudev-util-private.c b/src/libudev-util-private.c
new file mode 100644
index 0000000..08f0ba2
--- /dev/null
+++ b/src/libudev-util-private.c
@@ -0,0 +1,242 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2003-2009 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/param.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static int create_path(struct udev *udev, const char *path, bool selinux)
+{
+        char p[UTIL_PATH_SIZE];
+        char *pos;
+        struct stat stats;
+        int err;
+
+        util_strscpy(p, sizeof(p), path);
+        pos = strrchr(p, '/');
+        if (pos == NULL)
+                return 0;
+        while (pos != p && pos[-1] == '/')
+                pos--;
+        if (pos == p)
+                return 0;
+        pos[0] = '\0';
+
+        dbg(udev, "stat '%s'\n", p);
+        if (stat(p, &stats) == 0) {
+                if ((stats.st_mode & S_IFMT) == S_IFDIR)
+                        return 0;
+                else
+                        return -ENOTDIR;
+        }
+
+        err = util_create_path(udev, p);
+        if (err != 0)
+                return err;
+
+        dbg(udev, "mkdir '%s'\n", p);
+        if (selinux)
+                udev_selinux_setfscreatecon(udev, p, S_IFDIR|0755);
+        err = mkdir(p, 0755);
+        if (err != 0) {
+                err = -errno;
+                if (err == -EEXIST && stat(p, &stats) == 0) {
+                        if ((stats.st_mode & S_IFMT) == S_IFDIR)
+                                err = 0;
+                        else
+                                err = -ENOTDIR;
+                }
+        }
+        if (selinux)
+                udev_selinux_resetfscreatecon(udev);
+        return err;
+}
+
+int util_create_path(struct udev *udev, const char *path)
+{
+        return create_path(udev, path, false);
+}
+
+int util_create_path_selinux(struct udev *udev, const char *path)
+{
+        return create_path(udev, path, true);
+}
+
+int util_delete_path(struct udev *udev, const char *path)
+{
+        char p[UTIL_PATH_SIZE];
+        char *pos;
+        int err = 0;
+
+        if (path[0] == '/')
+                while(path[1] == '/')
+                        path++;
+        util_strscpy(p, sizeof(p), path);
+        pos = strrchr(p, '/');
+        if (pos == p || pos == NULL)
+                return 0;
+
+        for (;;) {
+                *pos = '\0';
+                pos = strrchr(p, '/');
+
+                /* don't remove the last one */
+                if ((pos == p) || (pos == NULL))
+                        break;
+
+                err = rmdir(p);
+                if (err < 0) {
+                        if (errno == ENOENT)
+                                err = 0;
+                        break;
+                }
+        }
+        return err;
+}
+
+uid_t util_lookup_user(struct udev *udev, const char *user)
+{
+        char *endptr;
+        size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+        char buf[buflen];
+        struct passwd pwbuf;
+        struct passwd *pw;
+        uid_t uid;
+
+        if (strcmp(user, "root") == 0)
+                return 0;
+        uid = strtoul(user, &endptr, 10);
+        if (endptr[0] == '\0')
+                return uid;
+
+        errno = getpwnam_r(user, &pwbuf, buf, buflen, &pw);
+        if (pw != NULL)
+                return pw->pw_uid;
+        if (errno == 0 || errno == ENOENT || errno == ESRCH)
+                err(udev, "specified user '%s' unknown\n", user);
+        else
+                err(udev, "error resolving user '%s': %m\n", user);
+        return 0;
+}
+
+gid_t util_lookup_group(struct udev *udev, const char *group)
+{
+        char *endptr;
+        size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
+        char *buf;
+        struct group grbuf;
+        struct group *gr;
+        gid_t gid = 0;
+
+        if (strcmp(group, "root") == 0)
+                return 0;
+        gid = strtoul(group, &endptr, 10);
+        if (endptr[0] == '\0')
+                return gid;
+        buf = NULL;
+        gid = 0;
+        for (;;) {
+                char *newbuf;
+
+                newbuf = realloc(buf, buflen);
+                if (!newbuf)
+                        break;
+                buf = newbuf;
+                errno = getgrnam_r(group, &grbuf, buf, buflen, &gr);
+                if (gr != NULL) {
+                        gid = gr->gr_gid;
+                } else if (errno == ERANGE) {
+                        buflen *= 2;
+                        continue;
+                } else if (errno == 0 || errno == ENOENT || errno == ESRCH) {
+                        err(udev, "specified group '%s' unknown\n", group);
+                } else {
+                        err(udev, "error resolving group '%s': %m\n", group);
+                }
+                break;
+        }
+        free(buf);
+        return gid;
+}
+
+/* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */
+int util_resolve_subsys_kernel(struct udev *udev, const char *string,
+                               char *result, size_t maxsize, int read_value)
+{
+        char temp[UTIL_PATH_SIZE];
+        char *subsys;
+        char *sysname;
+        struct udev_device *dev;
+        char *attr;
+
+        if (string[0] != '[')
+                return -1;
+
+        util_strscpy(temp, sizeof(temp), string);
+
+        subsys = &temp[1];
+
+        sysname = strchr(subsys, '/');
+        if (sysname == NULL)
+                return -1;
+        sysname[0] = '\0';
+        sysname = &sysname[1];
+
+        attr = strchr(sysname, ']');
+        if (attr == NULL)
+                return -1;
+        attr[0] = '\0';
+        attr = &attr[1];
+        if (attr[0] == '/')
+                attr = &attr[1];
+        if (attr[0] == '\0')
+                attr = NULL;
+
+        if (read_value && attr == NULL)
+                return -1;
+
+        dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname);
+        if (dev == NULL)
+                return -1;
+
+        if (read_value) {
+                const char *val;
+
+                val = udev_device_get_sysattr_value(dev, attr);
+                if (val != NULL)
+                        util_strscpy(result, maxsize, val);
+                else
+                        result[0] = '\0';
+                info(udev, "value '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result);
+        } else {
+                size_t l;
+                char *s;
+
+                s = result;
+                l = util_strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL);
+                if (attr != NULL)
+                        util_strpcpyl(&s, l, "/", attr, NULL);
+                info(udev, "path '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result);
+        }
+        udev_device_unref(dev);
+        return 0;
+}
diff --git a/src/libudev-util.c b/src/libudev-util.c
new file mode 100644
index 0000000..7e345f0
--- /dev/null
+++ b/src/libudev-util.c
@@ -0,0 +1,570 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-util
+ * @short_description: utils
+ */
+
+ssize_t util_get_sys_core_link_value(struct udev *udev, const char *slink, const char *syspath, char *value, size_t size)
+{
+        char path[UTIL_PATH_SIZE];
+        char target[UTIL_PATH_SIZE];
+        ssize_t len;
+        const char *pos;
+
+        util_strscpyl(path, sizeof(path), syspath, "/", slink, NULL);
+        len = readlink(path, target, sizeof(target));
+        if (len <= 0 || len == (ssize_t)sizeof(target))
+                return -1;
+        target[len] = '\0';
+        pos = strrchr(target, '/');
+        if (pos == NULL)
+                return -1;
+        pos = &pos[1];
+        dbg(udev, "resolved link to: '%s'\n", pos);
+        return util_strscpy(value, size, pos);
+}
+
+int util_resolve_sys_link(struct udev *udev, char *syspath, size_t size)
+{
+        char link_target[UTIL_PATH_SIZE];
+
+        ssize_t len;
+        int i;
+        int back;
+        char *base = NULL;
+
+        len = readlink(syspath, link_target, sizeof(link_target));
+        if (len <= 0 || len == (ssize_t)sizeof(link_target))
+                return -1;
+        link_target[len] = '\0';
+        dbg(udev, "path link '%s' points to '%s'\n", syspath, link_target);
+
+        for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
+                ;
+        dbg(udev, "base '%s', tail '%s', back %i\n", syspath, &link_target[back * 3], back);
+        for (i = 0; i <= back; i++) {
+                base = strrchr(syspath, '/');
+                if (base == NULL)
+                        return -EINVAL;
+                base[0] = '\0';
+        }
+        if (base == NULL)
+                return -EINVAL;
+        dbg(udev, "after moving back '%s'\n", syspath);
+        util_strscpyl(base, size - (base - syspath), "/", &link_target[back * 3], NULL);
+        return 0;
+}
+
+int util_log_priority(const char *priority)
+{
+        char *endptr;
+        int prio;
+
+        prio = strtol(priority, &endptr, 10);
+        if (endptr[0] == '\0' || isspace(endptr[0]))
+                return prio;
+        if (strncmp(priority, "err", 3) == 0)
+                return LOG_ERR;
+        if (strncmp(priority, "info", 4) == 0)
+                return LOG_INFO;
+        if (strncmp(priority, "debug", 5) == 0)
+                return LOG_DEBUG;
+        return 0;
+}
+
+size_t util_path_encode(const char *src, char *dest, size_t size)
+{
+        size_t i, j;
+
+        for (i = 0, j = 0; src[i] != '\0'; i++) {
+                if (src[i] == '/') {
+                        if (j+4 >= size) {
+                                j = 0;
+                                break;
+                        }
+                        memcpy(&dest[j], "\\x2f", 4);
+                        j += 4;
+                } else if (src[i] == '\\') {
+                        if (j+4 >= size) {
+                                j = 0;
+                                break;
+                        }
+                        memcpy(&dest[j], "\\x5c", 4);
+                        j += 4;
+                } else {
+                        if (j+1 >= size) {
+                                j = 0;
+                                break;
+                        }
+                        dest[j] = src[i];
+                        j++;
+                }
+        }
+        dest[j] = '\0';
+        return j;
+}
+
+size_t util_path_decode(char *s)
+{
+        size_t i, j;
+
+        for (i = 0, j = 0; s[i] != '\0'; j++) {
+                if (memcmp(&s[i], "\\x2f", 4) == 0) {
+                        s[j] = '/';
+                        i += 4;
+                } else if (memcmp(&s[i], "\\x5c", 4) == 0) {
+                        s[j] = '\\';
+                        i += 4;
+                } else {
+                        s[j] = s[i];
+                        i++;
+                }
+        }
+        s[j] = '\0';
+        return j;
+}
+
+void util_remove_trailing_chars(char *path, char c)
+{
+        size_t len;
+
+        if (path == NULL)
+                return;
+        len = strlen(path);
+        while (len > 0 && path[len-1] == c)
+                path[--len] = '\0';
+}
+
+/*
+ * Concatenates strings. In any case, terminates in _all_ cases with '\0'
+ * and moves the @dest pointer forward to the added '\0'. Returns the
+ * remaining size, and 0 if the string was truncated.
+ */
+size_t util_strpcpy(char **dest, size_t size, const char *src)
+{
+        size_t len;
+
+        len = strlen(src);
+        if (len >= size) {
+                if (size > 1)
+                        *dest = mempcpy(*dest, src, size-1);
+                size = 0;
+                *dest[0] = '\0';
+        } else {
+                if (len > 0) {
+                        *dest = mempcpy(*dest, src, len);
+                        size -= len;
+                }
+                *dest[0] = '\0';
+        }
+        return size;
+}
+
+/* concatenates list of strings, moves dest forward */
+size_t util_strpcpyl(char **dest, size_t size, const char *src, ...)
+{
+        va_list va;
+
+        va_start(va, src);
+        do {
+                size = util_strpcpy(dest, size, src);
+                src = va_arg(va, char *);
+        } while (src != NULL);
+        va_end(va);
+
+        return size;
+}
+
+/* copies string */
+size_t util_strscpy(char *dest, size_t size, const char *src)
+{
+        char *s;
+
+        s = dest;
+        return util_strpcpy(&s, size, src);
+}
+
+/* concatenates list of strings */
+size_t util_strscpyl(char *dest, size_t size, const char *src, ...)
+{
+        va_list va;
+        char *s;
+
+        va_start(va, src);
+        s = dest;
+        do {
+                size = util_strpcpy(&s, size, src);
+                src = va_arg(va, char *);
+        } while (src != NULL);
+        va_end(va);
+
+        return size;
+}
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+        unsigned char c = (unsigned char)str[0];
+
+        if (c < 0x80)
+                return 1;
+        if ((c & 0xe0) == 0xc0)
+                return 2;
+        if ((c & 0xf0) == 0xe0)
+                return 3;
+        if ((c & 0xf8) == 0xf0)
+                return 4;
+        if ((c & 0xfc) == 0xf8)
+                return 5;
+        if ((c & 0xfe) == 0xfc)
+                return 6;
+        return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+        int unichar;
+        int len;
+        int i;
+
+        len = utf8_encoded_expected_len(str);
+        switch (len) {
+        case 1:
+                return (int)str[0];
+        case 2:
+                unichar = str[0] & 0x1f;
+                break;
+        case 3:
+                unichar = (int)str[0] & 0x0f;
+                break;
+        case 4:
+                unichar = (int)str[0] & 0x07;
+                break;
+        case 5:
+                unichar = (int)str[0] & 0x03;
+                break;
+        case 6:
+                unichar = (int)str[0] & 0x01;
+                break;
+        default:
+                return -1;
+        }
+
+        for (i = 1; i < len; i++) {
+                if (((int)str[i] & 0xc0) != 0x80)
+                        return -1;
+                unichar <<= 6;
+                unichar |= (int)str[i] & 0x3f;
+        }
+
+        return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+        if (unichar < 0x80)
+                return 1;
+        if (unichar < 0x800)
+                return 2;
+        if (unichar < 0x10000)
+                return 3;
+        if (unichar < 0x200000)
+                return 4;
+        if (unichar < 0x4000000)
+                return 5;
+        return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+        if (unichar > 0x10ffff)
+                return 0;
+        if ((unichar & 0xfffff800) == 0xd800)
+                return 0;
+        if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+                return 0;
+        if ((unichar & 0xffff) == 0xffff)
+                return 0;
+        return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+        int len;
+        int unichar;
+        int i;
+
+        len = utf8_encoded_expected_len(str);
+        if (len == 0)
+                return -1;
+
+        /* ascii is valid */
+        if (len == 1)
+                return 1;
+
+        /* check if expected encoded chars are available */
+        for (i = 0; i < len; i++)
+                if ((str[i] & 0x80) != 0x80)
+                        return -1;
+
+        unichar = utf8_encoded_to_unichar(str);
+
+        /* check if encoded length matches encoded value */
+        if (utf8_unichar_to_encoded_len(unichar) != len)
+                return -1;
+
+        /* check if value has valid range */
+        if (!utf8_unichar_valid_range(unichar))
+                return -1;
+
+        return len;
+}
+
+int util_replace_whitespace(const char *str, char *to, size_t len)
+{
+        size_t i, j;
+
+        /* strip trailing whitespace */
+        len = strnlen(str, len);
+        while (len && isspace(str[len-1]))
+                len--;
+
+        /* strip leading whitespace */
+        i = 0;
+        while (isspace(str[i]) && (i < len))
+                i++;
+
+        j = 0;
+        while (i < len) {
+                /* substitute multiple whitespace with a single '_' */
+                if (isspace(str[i])) {
+                        while (isspace(str[i]))
+                                i++;
+                        to[j++] = '_';
+                }
+                to[j++] = str[i++];
+        }
+        to[j] = '\0';
+        return 0;
+}
+
+static int is_whitelisted(char c, const char *white)
+{
+        if ((c >= '0' && c <= '9') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c >= 'a' && c <= 'z') ||
+            strchr("#+-.:=@_", c) != NULL ||
+            (white != NULL && strchr(white, c) != NULL))
+                return 1;
+        return 0;
+}
+
+/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */
+int util_replace_chars(char *str, const char *white)
+{
+        size_t i = 0;
+        int replaced = 0;
+
+        while (str[i] != '\0') {
+                int len;
+
+                if (is_whitelisted(str[i], white)) {
+                        i++;
+                        continue;
+                }
+
+                /* accept hex encoding */
+                if (str[i] == '\\' && str[i+1] == 'x') {
+                        i += 2;
+                        continue;
+                }
+
+                /* accept valid utf8 */
+                len = utf8_encoded_valid_unichar(&str[i]);
+                if (len > 1) {
+                        i += len;
+                        continue;
+                }
+
+                /* if space is allowed, replace whitespace with ordinary space */
+                if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) {
+                        str[i] = ' ';
+                        i++;
+                        replaced++;
+                        continue;
+                }
+
+                /* everything else is replaced with '_' */
+                str[i] = '_';
+                i++;
+                replaced++;
+        }
+        return replaced;
+}
+
+/**
+ * udev_util_encode_string:
+ * @str: input string to be encoded
+ * @str_enc: output string to store the encoded input string
+ * @len: maximum size of the output string, which may be
+ *       four times as long as the input string
+ *
+ * Encode all potentially unsafe characters of a string to the
+ * corresponding 2 char hex value prefixed by '\x'.
+ *
+ * Returns: 0 if the entire string was copied, non-zero otherwise.
+ **/
+UDEV_EXPORT int udev_util_encode_string(const char *str, char *str_enc, size_t len)
+{
+        size_t i, j;
+
+        if (str == NULL || str_enc == NULL)
+                return -1;
+
+        for (i = 0, j = 0; str[i] != '\0'; i++) {
+                int seqlen;
+
+                seqlen = utf8_encoded_valid_unichar(&str[i]);
+                if (seqlen > 1) {
+                        if (len-j < (size_t)seqlen)
+                                goto err;
+                        memcpy(&str_enc[j], &str[i], seqlen);
+                        j += seqlen;
+                        i += (seqlen-1);
+                } else if (str[i] == '\\' || !is_whitelisted(str[i], NULL)) {
+                        if (len-j < 4)
+                                goto err;
+                        sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]);
+                        j += 4;
+                } else {
+                        if (len-j < 1)
+                                goto err;
+                        str_enc[j] = str[i];
+                        j++;
+                }
+        }
+        if (len-j < 1)
+                goto err;
+        str_enc[j] = '\0';
+        return 0;
+err:
+        return -1;
+}
+
+/*
+ * http://sites.google.com/site/murmurhash/
+ *
+ * All code is released to the public domain. For business purposes,
+ * Murmurhash is under the MIT license.
+ *
+ */
+static unsigned int murmur_hash2(const char *key, int len, unsigned int seed)
+{
+        /*
+         *  'm' and 'r' are mixing constants generated offline.
+         *  They're not really 'magic', they just happen to work well.
+         */
+        const unsigned int m = 0x5bd1e995;
+        const int r = 24;
+
+        /* initialize the hash to a 'random' value */
+        unsigned int h = seed ^ len;
+
+        /* mix 4 bytes at a time into the hash */
+        const unsigned char * data = (const unsigned char *)key;
+
+        while(len >= 4) {
+                unsigned int k = *(unsigned int *)data;
+
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+                h *= m;
+                h ^= k;
+
+                data += 4;
+                len -= 4;
+        }
+
+        /* handle the last few bytes of the input array */
+        switch(len) {
+        case 3:
+                h ^= data[2] << 16;
+        case 2:
+                h ^= data[1] << 8;
+        case 1:
+                h ^= data[0];
+                h *= m;
+        };
+
+        /* do a few final mixes of the hash to ensure the last few bytes are well-incorporated */
+        h ^= h >> 13;
+        h *= m;
+        h ^= h >> 15;
+
+        return h;
+}
+
+unsigned int util_string_hash32(const char *str)
+{
+        return murmur_hash2(str, strlen(str), 0);
+}
+
+/* get a bunch of bit numbers out of the hash, and set the bits in our bit field */
+uint64_t util_string_bloom64(const char *str)
+{
+        uint64_t bits = 0;
+        unsigned int hash = util_string_hash32(str);
+
+        bits |= 1LLU << (hash & 63);
+        bits |= 1LLU << ((hash >> 6) & 63);
+        bits |= 1LLU << ((hash >> 12) & 63);
+        bits |= 1LLU << ((hash >> 18) & 63);
+        return bits;
+}
+
+#define USEC_PER_SEC  1000000ULL
+#define NSEC_PER_USEC 1000ULL
+unsigned long long ts_usec(const struct timespec *ts)
+{
+        return (unsigned long long) ts->tv_sec * USEC_PER_SEC +
+               (unsigned long long) ts->tv_nsec / NSEC_PER_USEC;
+}
+
+unsigned long long now_usec(void)
+{
+        struct timespec ts;
+
+        if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+                return 0;
+        return ts_usec(&ts);
+}
diff --git a/src/libudev.c b/src/libudev.c
new file mode 100644
index 0000000..d954dae
--- /dev/null
+++ b/src/libudev.c
@@ -0,0 +1,457 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev
+ * @short_description: libudev context
+ *
+ * The context contains the default values read from the udev config file,
+ * and is passed to all library operations.
+ */
+
+/**
+ * udev:
+ *
+ * Opaque object representing the library context.
+ */
+struct udev {
+        int refcount;
+        void (*log_fn)(struct udev *udev,
+                       int priority, const char *file, int line, const char *fn,
+                       const char *format, va_list args);
+        void *userdata;
+        char *sys_path;
+        char *dev_path;
+        char *rules_path[4];
+        unsigned long long rules_path_ts[4];
+        int rules_path_count;
+        char *run_path;
+        struct udev_list properties_list;
+        int log_priority;
+};
+
+void udev_log(struct udev *udev,
+              int priority, const char *file, int line, const char *fn,
+              const char *format, ...)
+{
+        va_list args;
+
+        va_start(args, format);
+        udev->log_fn(udev, priority, file, line, fn, format, args);
+        va_end(args);
+}
+
+static void log_stderr(struct udev *udev,
+                       int priority, const char *file, int line, const char *fn,
+                       const char *format, va_list args)
+{
+        fprintf(stderr, "libudev: %s: ", fn);
+        vfprintf(stderr, format, args);
+}
+
+/**
+ * udev_get_userdata:
+ * @udev: udev library context
+ *
+ * Retrieve stored data pointer from library context. This might be useful
+ * to access from callbacks like a custom logging function.
+ *
+ * Returns: stored userdata
+ **/
+UDEV_EXPORT void *udev_get_userdata(struct udev *udev)
+{
+        if (udev == NULL)
+                return NULL;
+        return udev->userdata;
+}
+
+/**
+ * udev_set_userdata:
+ * @udev: udev library context
+ * @userdata: data pointer
+ *
+ * Store custom @userdata in the library context.
+ **/
+UDEV_EXPORT void udev_set_userdata(struct udev *udev, void *userdata)
+{
+        if (udev == NULL)
+                return;
+        udev->userdata = userdata;
+}
+
+static char *set_value(char **s, const char *v)
+{
+        free(*s);
+        *s = strdup(v);
+        util_remove_trailing_chars(*s, '/');
+        return *s;
+}
+
+/**
+ * udev_new:
+ *
+ * Create udev library context. This reads the udev configuration
+ * file, and fills in the default values.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev library context.
+ *
+ * Returns: a new udev library context
+ **/
+UDEV_EXPORT struct udev *udev_new(void)
+{
+        struct udev *udev;
+        const char *env;
+        char *config_file = NULL;
+        FILE *f;
+
+        udev = calloc(1, sizeof(struct udev));
+        if (udev == NULL)
+                return NULL;
+        udev->refcount = 1;
+        udev->log_fn = log_stderr;
+        udev->log_priority = LOG_ERR;
+        udev_list_init(udev, &udev->properties_list, true);
+
+        /* custom config file */
+        env = getenv("UDEV_CONFIG_FILE");
+        if (env != NULL) {
+                if (set_value(&config_file, env) == NULL)
+                        goto err;
+                udev_add_property(udev, "UDEV_CONFIG_FILE", config_file);
+        }
+
+        /* default config file */
+        if (config_file == NULL)
+                config_file = strdup(SYSCONFDIR "/udev/udev.conf");
+        if (config_file == NULL)
+                goto err;
+
+        f = fopen(config_file, "re");
+        if (f != NULL) {
+                char line[UTIL_LINE_SIZE];
+                int line_nr = 0;
+
+                while (fgets(line, sizeof(line), f)) {
+                        size_t len;
+                        char *key;
+                        char *val;
+
+                        line_nr++;
+
+                        /* find key */
+                        key = line;
+                        while (isspace(key[0]))
+                                key++;
+
+                        /* comment or empty line */
+                        if (key[0] == '#' || key[0] == '\0')
+                                continue;
+
+                        /* split key/value */
+                        val = strchr(key, '=');
+                        if (val == NULL) {
+                                err(udev, "missing <key>=<value> in '%s'[%i], skip line\n", config_file, line_nr);
+                                continue;
+                        }
+                        val[0] = '\0';
+                        val++;
+
+                        /* find value */
+                        while (isspace(val[0]))
+                                val++;
+
+                        /* terminate key */
+                        len = strlen(key);
+                        if (len == 0)
+                                continue;
+                        while (isspace(key[len-1]))
+                                len--;
+                        key[len] = '\0';
+
+                        /* terminate value */
+                        len = strlen(val);
+                        if (len == 0)
+                                continue;
+                        while (isspace(val[len-1]))
+                                len--;
+                        val[len] = '\0';
+
+                        if (len == 0)
+                                continue;
+
+                        /* unquote */
+                        if (val[0] == '"' || val[0] == '\'') {
+                                if (val[len-1] != val[0]) {
+                                        err(udev, "inconsistent quoting in '%s'[%i], skip line\n", config_file, line_nr);
+                                        continue;
+                                }
+                                val[len-1] = '\0';
+                                val++;
+                        }
+
+                        if (strcmp(key, "udev_log") == 0) {
+                                udev_set_log_priority(udev, util_log_priority(val));
+                                continue;
+                        }
+                        if (strcmp(key, "udev_root") == 0) {
+                                set_value(&udev->dev_path, val);
+                                continue;
+                        }
+                        if (strcmp(key, "udev_run") == 0) {
+                                set_value(&udev->run_path, val);
+                                continue;
+                        }
+                        if (strcmp(key, "udev_sys") == 0) {
+                                set_value(&udev->sys_path, val);
+                                continue;
+                        }
+                        if (strcmp(key, "udev_rules") == 0) {
+                                set_value(&udev->rules_path[0], val);
+                                udev->rules_path_count = 1;
+                                continue;
+                        }
+                }
+                fclose(f);
+        }
+
+        /* environment overrides config */
+        env = getenv("UDEV_LOG");
+        if (env != NULL)
+                udev_set_log_priority(udev, util_log_priority(env));
+
+        /* set defaults */
+        if (udev->dev_path == NULL)
+                if (set_value(&udev->dev_path, "/dev") == NULL)
+                        goto err;
+
+        if (udev->sys_path == NULL)
+                if (set_value(&udev->sys_path, "/sys") == NULL)
+                        goto err;
+
+        if (udev->run_path == NULL)
+                if (set_value(&udev->run_path, "/run/udev") == NULL)
+                        goto err;
+
+        if (udev->rules_path[0] == NULL) {
+                /* /usr/lib/udev -- system rules */
+                udev->rules_path[0] = strdup(PKGLIBEXECDIR "/rules.d");
+                if (!udev->rules_path[0])
+                        goto err;
+
+                /* /run/udev -- runtime rules */
+                if (asprintf(&udev->rules_path[2], "%s/rules.d", udev->run_path) < 0)
+                        goto err;
+
+                /* /etc/udev -- local administration rules */
+                udev->rules_path[1] = strdup(SYSCONFDIR "/udev/rules.d");
+                if (!udev->rules_path[1])
+                        goto err;
+
+                udev->rules_path_count = 3;
+        }
+
+        dbg(udev, "context %p created\n", udev);
+        dbg(udev, "log_priority=%d\n", udev->log_priority);
+        dbg(udev, "config_file='%s'\n", config_file);
+        dbg(udev, "dev_path='%s'\n", udev->dev_path);
+        dbg(udev, "sys_path='%s'\n", udev->sys_path);
+        dbg(udev, "run_path='%s'\n", udev->run_path);
+        dbg(udev, "rules_path='%s':'%s':'%s'\n", udev->rules_path[0], udev->rules_path[1], udev->rules_path[2]);
+        free(config_file);
+        return udev;
+err:
+        free(config_file);
+        err(udev, "context creation failed\n");
+        udev_unref(udev);
+        return NULL;
+}
+
+/**
+ * udev_ref:
+ * @udev: udev library context
+ *
+ * Take a reference of the udev library context.
+ *
+ * Returns: the passed udev library context
+ **/
+UDEV_EXPORT struct udev *udev_ref(struct udev *udev)
+{
+        if (udev == NULL)
+                return NULL;
+        udev->refcount++;
+        return udev;
+}
+
+/**
+ * udev_unref:
+ * @udev: udev library context
+ *
+ * Drop a reference of the udev library context. If the refcount
+ * reaches zero, the resources of the context will be released.
+ *
+ **/
+UDEV_EXPORT void udev_unref(struct udev *udev)
+{
+        if (udev == NULL)
+                return;
+        udev->refcount--;
+        if (udev->refcount > 0)
+                return;
+        udev_list_cleanup(&udev->properties_list);
+        free(udev->dev_path);
+        free(udev->sys_path);
+        free(udev->rules_path[0]);
+        free(udev->rules_path[1]);
+        free(udev->rules_path[2]);
+        free(udev->run_path);
+        dbg(udev, "context %p released\n", udev);
+        free(udev);
+}
+
+/**
+ * udev_set_log_fn:
+ * @udev: udev library context
+ * @log_fn: function to be called for logging messages
+ *
+ * The built-in logging writes to stderr. It can be
+ * overridden by a custom function, to plug log messages
+ * into the users' logging functionality.
+ *
+ **/
+UDEV_EXPORT void udev_set_log_fn(struct udev *udev,
+                     void (*log_fn)(struct udev *udev,
+                                    int priority, const char *file, int line, const char *fn,
+                                    const char *format, va_list args))
+{
+        udev->log_fn = log_fn;
+        info(udev, "custom logging function %p registered\n", log_fn);
+}
+
+/**
+ * udev_get_log_priority:
+ * @udev: udev library context
+ *
+ * The initial logging priority is read from the udev config file
+ * at startup.
+ *
+ * Returns: the current logging priority
+ **/
+UDEV_EXPORT int udev_get_log_priority(struct udev *udev)
+{
+        return udev->log_priority;
+}
+
+/**
+ * udev_set_log_priority:
+ * @udev: udev library context
+ * @priority: the new logging priority
+ *
+ * Set the current logging priority. The value controls which messages
+ * are logged.
+ **/
+UDEV_EXPORT void udev_set_log_priority(struct udev *udev, int priority)
+{
+        char num[32];
+
+        udev->log_priority = priority;
+        snprintf(num, sizeof(num), "%u", udev->log_priority);
+        udev_add_property(udev, "UDEV_LOG", num);
+}
+
+int udev_get_rules_path(struct udev *udev, char **path[], unsigned long long *stamp_usec[])
+{
+        *path = udev->rules_path;
+        if (stamp_usec)
+                *stamp_usec = udev->rules_path_ts;
+        return udev->rules_path_count;
+}
+
+/**
+ * udev_get_sys_path:
+ * @udev: udev library context
+ *
+ * Retrieve the sysfs mount point. The default is "/sys". For
+ * testing purposes, it can be overridden with udev_sys=
+ * in the udev configuration file.
+ *
+ * Returns: the sys mount point
+ **/
+UDEV_EXPORT const char *udev_get_sys_path(struct udev *udev)
+{
+        if (udev == NULL)
+                return NULL;
+        return udev->sys_path;
+}
+
+/**
+ * udev_get_dev_path:
+ * @udev: udev library context
+ *
+ * Retrieve the device directory path. The default value is "/dev",
+ * the actual value may be overridden in the udev configuration
+ * file.
+ *
+ * Returns: the device directory path
+ **/
+UDEV_EXPORT const char *udev_get_dev_path(struct udev *udev)
+{
+        if (udev == NULL)
+                return NULL;
+        return udev->dev_path;
+}
+
+/**
+ * udev_get_run_path:
+ * @udev: udev library context
+ *
+ * Retrieve the udev runtime directory path. The default is "/run/udev".
+ *
+ * Returns: the runtime directory path
+ **/
+UDEV_EXPORT const char *udev_get_run_path(struct udev *udev)
+{
+        if (udev == NULL)
+                return NULL;
+        return udev->run_path;
+}
+
+struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value)
+{
+        if (value == NULL) {
+                struct udev_list_entry *list_entry;
+
+                list_entry = udev_get_properties_list_entry(udev);
+                list_entry = udev_list_entry_get_by_name(list_entry, key);
+                if (list_entry != NULL)
+                        udev_list_entry_delete(list_entry);
+                return NULL;
+        }
+        return udev_list_entry_add(&udev->properties_list, key, value);
+}
+
+struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev)
+{
+        return udev_list_get_entry(&udev->properties_list);
+}
diff --git a/src/libudev.h b/src/libudev.h
new file mode 100644
index 0000000..10e098d
--- /dev/null
+++ b/src/libudev.h
@@ -0,0 +1,189 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LIBUDEV_H_
+#define _LIBUDEV_H_
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * udev - library context
+ *
+ * reads the udev config and system environment
+ * allows custom logging
+ */
+struct udev;
+struct udev *udev_ref(struct udev *udev);
+void udev_unref(struct udev *udev);
+struct udev *udev_new(void);
+void udev_set_log_fn(struct udev *udev,
+                            void (*log_fn)(struct udev *udev,
+                                           int priority, const char *file, int line, const char *fn,
+                                           const char *format, va_list args));
+int udev_get_log_priority(struct udev *udev);
+void udev_set_log_priority(struct udev *udev, int priority);
+const char *udev_get_sys_path(struct udev *udev);
+const char *udev_get_dev_path(struct udev *udev);
+const char *udev_get_run_path(struct udev *udev);
+void *udev_get_userdata(struct udev *udev);
+void udev_set_userdata(struct udev *udev, void *userdata);
+
+/*
+ * udev_list
+ *
+ * access to libudev generated lists
+ */
+struct udev_list_entry;
+struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry);
+struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name);
+const char *udev_list_entry_get_name(struct udev_list_entry *list_entry);
+const char *udev_list_entry_get_value(struct udev_list_entry *list_entry);
+/**
+ * udev_list_entry_foreach:
+ * @list_entry: entry to store the current position
+ * @first_entry: first entry to start with
+ *
+ * Helper to iterate over all entries of a list.
+ */
+#define udev_list_entry_foreach(list_entry, first_entry) \
+        for (list_entry = first_entry; \
+             list_entry != NULL; \
+             list_entry = udev_list_entry_get_next(list_entry))
+
+/*
+ * udev_device
+ *
+ * access to sysfs/kernel devices
+ */
+struct udev_device;
+struct udev_device *udev_device_ref(struct udev_device *udev_device);
+void udev_device_unref(struct udev_device *udev_device);
+struct udev *udev_device_get_udev(struct udev_device *udev_device);
+struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath);
+struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum);
+struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname);
+struct udev_device *udev_device_new_from_environment(struct udev *udev);
+/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */
+struct udev_device *udev_device_get_parent(struct udev_device *udev_device);
+struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device,
+                                                                  const char *subsystem, const char *devtype);
+/* retrieve device properties */
+const char *udev_device_get_devpath(struct udev_device *udev_device);
+const char *udev_device_get_subsystem(struct udev_device *udev_device);
+const char *udev_device_get_devtype(struct udev_device *udev_device);
+const char *udev_device_get_syspath(struct udev_device *udev_device);
+const char *udev_device_get_sysname(struct udev_device *udev_device);
+const char *udev_device_get_sysnum(struct udev_device *udev_device);
+const char *udev_device_get_devnode(struct udev_device *udev_device);
+int udev_device_get_is_initialized(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device);
+const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key);
+const char *udev_device_get_driver(struct udev_device *udev_device);
+dev_t udev_device_get_devnum(struct udev_device *udev_device);
+const char *udev_device_get_action(struct udev_device *udev_device);
+unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device);
+unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device);
+const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr);
+int udev_device_has_tag(struct udev_device *udev_device, const char *tag);
+
+/*
+ * udev_monitor
+ *
+ * access to kernel uevents and udev events
+ */
+struct udev_monitor;
+struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor);
+void udev_monitor_unref(struct udev_monitor *udev_monitor);
+struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor);
+/* kernel and udev generated events over netlink */
+struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);
+/* custom socket (use netlink and filters instead) */
+struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path);
+/* bind socket */
+int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
+int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
+struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);
+/* in-kernel socket filters to select messages that get delivered to a listener */
+int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor,
+                                                    const char *subsystem, const char *devtype);
+int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag);
+int udev_monitor_filter_update(struct udev_monitor *udev_monitor);
+int udev_monitor_filter_remove(struct udev_monitor *udev_monitor);
+
+/*
+ * udev_enumerate
+ *
+ * search sysfs for specific devices and provide a sorted list
+ */
+struct udev_enumerate;
+struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate);
+void udev_enumerate_unref(struct udev_enumerate *udev_enumerate);
+struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate);
+struct udev_enumerate *udev_enumerate_new(struct udev *udev);
+/* device properties filter */
+int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value);
+int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname);
+int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag);
+int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent);
+int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate);
+int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath);
+/* run enumeration with active filters */
+int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate);
+int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate);
+/* return device list */
+struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate);
+
+/*
+ * udev_queue
+ *
+ * access to the currently running udev events
+ */
+struct udev_queue;
+struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue);
+void udev_queue_unref(struct udev_queue *udev_queue);
+struct udev *udev_queue_get_udev(struct udev_queue *udev_queue);
+struct udev_queue *udev_queue_new(struct udev *udev);
+unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue);
+unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue);
+int udev_queue_get_udev_is_active(struct udev_queue *udev_queue);
+int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue);
+int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum);
+int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+                                               unsigned long long int start, unsigned long long int end);
+struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue);
+
+/*
+ * udev_util
+ *
+ * udev specific utilities
+ */
+int udev_util_encode_string(const char *str, char *str_enc, size_t len);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/src/libudev.pc.in b/src/libudev.pc.in
new file mode 100644
index 0000000..c9a47fc
--- /dev/null
+++ b/src/libudev.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libudev
+Description: Library to access udev device information
+Version: @VERSION@
+Libs: -L${libdir} -ludev -lrt
+Libs.private:
+Cflags: -I${includedir}
diff --git a/src/mtd_probe/75-probe_mtd.rules b/src/mtd_probe/75-probe_mtd.rules
new file mode 100644
index 0000000..c0e0839
--- /dev/null
+++ b/src/mtd_probe/75-probe_mtd.rules
@@ -0,0 +1,8 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add", GOTO="mtd_probe_end"
+
+KERNEL=="mtd*ro", IMPORT{program}="mtd_probe $devnode"
+KERNEL=="mtd*ro", ENV{MTD_FTL}=="smartmedia", IMPORT{builtin}="kmod load sm_ftl"
+
+LABEL="mtd_probe_end"
diff --git a/src/mtd_probe/mtd_probe.c b/src/mtd_probe/mtd_probe.c
new file mode 100644
index 0000000..1aa08d3
--- /dev/null
+++ b/src/mtd_probe/mtd_probe.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 - Maxim Levitsky
+ *
+ * mtd_probe is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mtd_probe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mtd_probe; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+#include "mtd_probe.h"
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <mtd/mtd-user.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+int main(int argc, char** argv)
+{
+        if (argc != 2) {
+                printf("usage: mtd_probe /dev/mtd[n]\n");
+                return 1;
+        }
+
+        int mtd_fd = open(argv[1], O_RDONLY);
+        if (mtd_fd == -1) {
+                perror("open");
+                exit(-1);
+        }
+
+        mtd_info_t mtd_info;
+        int error = ioctl(mtd_fd, MEMGETINFO, &mtd_info);
+        if (error == -1) {
+                perror("ioctl");
+                exit(-1);
+        }
+
+        probe_smart_media(mtd_fd, &mtd_info);
+        return -1;
+}
diff --git a/src/mtd_probe/mtd_probe.h b/src/mtd_probe/mtd_probe.h
new file mode 100644
index 0000000..2a37ede
--- /dev/null
+++ b/src/mtd_probe/mtd_probe.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 - Maxim Levitsky
+ *
+ * mtd_probe is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mtd_probe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mtd_probe; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+
+#include <mtd/mtd-user.h>
+
+/* Full oob structure as written on the flash */
+struct sm_oob {
+        uint32_t reserved;
+        uint8_t data_status;
+        uint8_t block_status;
+        uint8_t lba_copy1[2];
+        uint8_t ecc2[3];
+        uint8_t lba_copy2[2];
+        uint8_t ecc1[3];
+} __attribute__((packed));
+
+
+/* one sector is always 512 bytes, but it can consist of two nand pages */
+#define SM_SECTOR_SIZE                512
+
+/* oob area is also 16 bytes, but might be from two pages */
+#define SM_OOB_SIZE                16
+
+/* This is maximum zone size, and all devices that have more that one zone
+   have this size */
+#define SM_MAX_ZONE_SIZE         1024
+
+/* support for small page nand */
+#define SM_SMALL_PAGE                 256
+#define SM_SMALL_OOB_SIZE        8
+
+
+void probe_smart_media(int mtd_fd, mtd_info_t *info);
diff --git a/src/mtd_probe/probe_smartmedia.c b/src/mtd_probe/probe_smartmedia.c
new file mode 100644
index 0000000..b3cdefc
--- /dev/null
+++ b/src/mtd_probe/probe_smartmedia.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 - Maxim Levitsky
+ *
+ * mtd_probe is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mtd_probe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mtd_probe; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <mtd/mtd-user.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "mtd_probe.h"
+
+static const uint8_t cis_signature[] = {
+        0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
+};
+
+
+void probe_smart_media(int mtd_fd, mtd_info_t* info)
+{
+        char* cis_buffer = malloc(SM_SECTOR_SIZE);
+
+        if (!cis_buffer)
+                return;
+
+        if (info->type != MTD_NANDFLASH)
+                goto exit;
+
+        int sector_size = info->writesize;
+        int block_size = info->erasesize;
+        int size_in_megs = info->size / (1024 * 1024);
+        int spare_count;
+
+
+        if (sector_size != SM_SECTOR_SIZE && sector_size != SM_SMALL_PAGE)
+                goto exit;
+
+        switch(size_in_megs) {
+        case 1:
+        case 2:
+                spare_count = 6;
+                break;
+        case 4:
+                spare_count = 12;
+                break;
+        default:
+                spare_count = 24;
+                break;
+        }
+
+
+        int offset;
+        int cis_found = 0;
+
+        for (offset = 0 ; offset < block_size * spare_count ;
+                                                offset += sector_size) {
+
+                lseek(mtd_fd, SEEK_SET, offset);
+                if (read(mtd_fd, cis_buffer, SM_SECTOR_SIZE) == SM_SECTOR_SIZE){
+                        cis_found = 1;
+                        break;
+                }
+        }
+
+        if (!cis_found)
+                goto exit;
+
+        if (memcmp(cis_buffer, cis_signature, sizeof(cis_signature)) != 0 &&
+                (memcmp(cis_buffer + SM_SMALL_PAGE, cis_signature,
+                        sizeof(cis_signature)) != 0))
+                goto exit;
+
+        printf("MTD_FTL=smartmedia\n");
+        free(cis_buffer);
+        exit(0);
+exit:
+        free(cis_buffer);
+        return;
+}
diff --git a/lib/udev/rules.d/75-cd-aliases-generator.rules b/src/rule_generator/75-cd-aliases-generator.rules
similarity index 100%
rename from lib/udev/rules.d/75-cd-aliases-generator.rules
rename to src/rule_generator/75-cd-aliases-generator.rules
diff --git a/lib/udev/rules.d/75-persistent-net-generator.rules b/src/rule_generator/75-persistent-net-generator.rules
similarity index 97%
rename from lib/udev/rules.d/75-persistent-net-generator.rules
rename to src/rule_generator/75-persistent-net-generator.rules
index 539807c..4f80573 100644
--- a/lib/udev/rules.d/75-persistent-net-generator.rules
+++ b/src/rule_generator/75-persistent-net-generator.rules
@@ -61,6 +61,8 @@
 ENV{MATCHADDR}=="52:54:ab:*", GOTO="globally_administered_whitelist"
 # Kingston Technologies
 ENV{MATCHADDR}=="e2:0c:0f:*", GOTO="globally_administered_whitelist"
+# Xensource
+ENV{MATCHADDR}=="00:16:3e:*", GOTO="globally_administered_whitelist"
 
 # match interface dev_id
 ATTR{dev_id}=="?*", ENV{MATCHDEVID}="$attr{dev_id}"
@@ -98,4 +100,3 @@
 ENV{INTERFACE_NEW}=="?*", NAME="$env{INTERFACE_NEW}"
 
 LABEL="persistent_net_generator_end"
-
diff --git a/src/rule_generator/rule_generator.functions b/src/rule_generator/rule_generator.functions
new file mode 100644
index 0000000..2eec1b6
--- /dev/null
+++ b/src/rule_generator/rule_generator.functions
@@ -0,0 +1,113 @@
+# functions used by the udev rule generator
+
+# Copyright (C) 2006 Marco d'Itri <md@Linux.IT>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+PATH='/usr/bin:/bin:/usr/sbin:/sbin'
+
+# Read a single line from file $1 in the $DEVPATH directory.
+# The function must not return an error even if the file does not exist.
+sysread() {
+        local file="$1"
+        [ -e "/sys$DEVPATH/$file" ] || return 0
+        local value
+        read value < "/sys$DEVPATH/$file" || return 0
+        echo "$value"
+}
+
+sysreadlink() {
+        local file="$1"
+        [ -e "/sys$DEVPATH/$file" ] || return 0
+        readlink -f /sys$DEVPATH/$file 2> /dev/null || true
+}
+
+# Return true if a directory is writeable.
+writeable() {
+        if ln -s test-link $1/.is-writeable 2> /dev/null; then
+                rm -f $1/.is-writeable
+                return 0
+        else
+                return 1
+        fi
+}
+
+# Create a lock file for the current rules file.
+lock_rules_file() {
+        RUNDIR=$(udevadm info --run)
+        [ -e "$RUNDIR" ] || return 0
+
+        RULES_LOCK="$RUNDIR/.lock-${RULES_FILE##*/}"
+
+        retry=30
+        while ! mkdir $RULES_LOCK 2> /dev/null; do
+                if [ $retry -eq 0 ]; then
+                         echo "Cannot lock $RULES_FILE!" >&2
+                         exit 2
+                fi
+                sleep 1
+                retry=$(($retry - 1))
+        done
+}
+
+unlock_rules_file() {
+        [ "$RULES_LOCK" ] || return 0
+        rmdir $RULES_LOCK || true
+}
+
+# Choose the real rules file if it is writeable or a temporary file if not.
+# Both files should be checked later when looking for existing rules.
+choose_rules_file() {
+        RUNDIR=$(udevadm info --run)
+        local tmp_rules_file="$RUNDIR/tmp-rules--${RULES_FILE##*/}"
+        [ -e "$RULES_FILE" -o -e "$tmp_rules_file" ] || PRINT_HEADER=1
+
+        if writeable ${RULES_FILE%/*}; then
+                RO_RULES_FILE='/dev/null'
+        else
+                RO_RULES_FILE=$RULES_FILE
+                RULES_FILE=$tmp_rules_file
+        fi
+}
+
+# Return the name of the first free device.
+raw_find_next_available() {
+        local links="$1"
+
+        local basename=${links%%[ 0-9]*}
+        local max=-1
+        for name in $links; do
+                local num=${name#$basename}
+                [ "$num" ] || num=0
+                [ $num -gt $max ] && max=$num
+        done
+
+        local max=$(($max + 1))
+        # "name0" actually is just "name"
+        [ $max -eq 0 ] && return
+        echo "$max"
+}
+
+# Find all rules matching a key (with action) and a pattern.
+find_all_rules() {
+        local key="$1"
+        local linkre="$2"
+        local match="$3"
+
+        local search='.*[[:space:],]'"$key"'"('"$linkre"')".*'
+        echo $(sed -n -r -e 's/^#.*//' -e "${match}s/${search}/\1/p" \
+                $RO_RULES_FILE \
+                $([ -e $RULES_FILE ] && echo $RULES_FILE) \
+                2>/dev/null)
+}
diff --git a/src/rule_generator/write_cd_rules b/src/rule_generator/write_cd_rules
new file mode 100644
index 0000000..645b9cd
--- /dev/null
+++ b/src/rule_generator/write_cd_rules
@@ -0,0 +1,126 @@
+#!/bin/sh -e
+
+# This script is run if an optical drive lacks a rule for persistent naming.
+#
+# It adds symlinks for optical drives based on the device class determined
+# by cdrom_id and used ID_PATH to identify the device.
+
+# (C) 2006 Marco d'Itri <md@Linux.IT>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# debug, if UDEV_LOG=<debug>
+if [ -n "$UDEV_LOG" ]; then
+        if [ "$UDEV_LOG" -ge 7 ]; then
+                set -x
+        fi
+fi
+
+RULES_FILE="/etc/udev/rules.d/70-persistent-cd.rules"
+
+. /lib/udev/rule_generator.functions
+
+find_next_available() {
+        raw_find_next_available "$(find_all_rules 'SYMLINK\+=' "$1")"
+}
+
+write_rule() {
+        local match="$1"
+        local link="$2"
+        local comment="$3"
+
+        {
+        if [ "$PRINT_HEADER" ]; then
+                PRINT_HEADER=
+                echo "# This file was automatically generated by the $0"
+                echo "# program, run by the cd-aliases-generator.rules rules file."
+                echo "#"
+                echo "# You can modify it, as long as you keep each rule on a single"
+                echo "# line, and set the \$GENERATED variable."
+                echo ""
+        fi
+
+        [ "$comment" ] && echo "# $comment"
+        echo "$match, SYMLINK+=\"$link\", ENV{GENERATED}=\"1\""
+        } >> $RULES_FILE
+        SYMLINKS="$SYMLINKS $link"
+}
+
+if [ -z "$DEVPATH" ]; then
+        echo "Missing \$DEVPATH." >&2
+        exit 1
+fi
+if [ -z "$ID_CDROM" ]; then
+        echo "$DEVPATH is not a CD reader." >&2
+        exit 1
+fi
+
+if [ "$1" ]; then
+        METHOD="$1"
+else
+        METHOD='by-path'
+fi
+
+case "$METHOD" in
+        by-path)
+        if [ -z "$ID_PATH" ]; then
+                echo "$DEVPATH not supported by path_id. by-id may work." >&2
+                exit 1
+        fi
+        RULE="ENV{ID_PATH}==\"$ID_PATH\""
+        ;;
+
+        by-id)
+        if [ "$ID_SERIAL" ]; then
+                RULE="ENV{ID_SERIAL}==\"$ID_SERIAL\""
+        elif [ "$ID_MODEL" -a "$ID_REVISION" ]; then
+                RULE="ENV{ID_MODEL}==\"$ID_MODEL\", ENV{ID_REVISION}==\"$ID_REVISION\""
+        else
+                echo "$DEVPATH not supported by ata_id. by-path may work." >&2
+                exit 1
+        fi
+        ;;
+
+        *)
+        echo "Invalid argument (must be either by-path or by-id)." >&2
+        exit 1
+        ;;
+esac
+
+# Prevent concurrent processes from modifying the file at the same time.
+lock_rules_file
+
+# Check if the rules file is writeable.
+choose_rules_file
+
+link_num=$(find_next_available 'cdrom[0-9]*')
+
+match="SUBSYSTEM==\"block\", ENV{ID_CDROM}==\"?*\", $RULE"
+
+comment="$ID_MODEL ($ID_PATH)"
+
+        write_rule "$match" "cdrom$link_num" "$comment"
+[ "$ID_CDROM_CD_R" -o "$ID_CDROM_CD_RW" ] && \
+        write_rule "$match" "cdrw$link_num"
+[ "$ID_CDROM_DVD" ] && \
+        write_rule "$match" "dvd$link_num"
+[ "$ID_CDROM_DVD_R" -o "$ID_CDROM_DVD_RW" -o "$ID_CDROM_DVD_RAM" ] && \
+        write_rule "$match" "dvdrw$link_num"
+echo >> $RULES_FILE
+
+unlock_rules_file
+
+echo $SYMLINKS
+
+exit 0
diff --git a/src/rule_generator/write_net_rules b/src/rule_generator/write_net_rules
new file mode 100644
index 0000000..bcea4b0
--- /dev/null
+++ b/src/rule_generator/write_net_rules
@@ -0,0 +1,141 @@
+#!/bin/sh -e
+
+# This script is run to create persistent network device naming rules
+# based on properties of the device.
+# If the interface needs to be renamed, INTERFACE_NEW=<name> will be printed
+# on stdout to allow udev to IMPORT it.
+
+# variables used to communicate:
+#   MATCHADDR             MAC address used for the match
+#   MATCHID               bus_id used for the match
+#   MATCHDEVID            dev_id used for the match
+#   MATCHDRV              driver name used for the match
+#   MATCHIFTYPE           interface type match
+#   COMMENT               comment to add to the generated rule
+#   INTERFACE_NAME        requested name supplied by external tool
+#   INTERFACE_NEW         new interface name returned by rule writer
+
+# Copyright (C) 2006 Marco d'Itri <md@Linux.IT>
+# Copyright (C) 2007 Kay Sievers <kay.sievers@vrfy.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# debug, if UDEV_LOG=<debug>
+if [ -n "$UDEV_LOG" ]; then
+        if [ "$UDEV_LOG" -ge 7 ]; then
+                set -x
+        fi
+fi
+
+RULES_FILE='/etc/udev/rules.d/70-persistent-net.rules'
+
+. /lib/udev/rule_generator.functions
+
+interface_name_taken() {
+        local value="$(find_all_rules 'NAME=' $INTERFACE)"
+        if [ "$value" ]; then
+                return 0
+        else
+                return 1
+        fi
+}
+
+find_next_available() {
+        raw_find_next_available "$(find_all_rules 'NAME=' "$1")"
+}
+
+write_rule() {
+        local match="$1"
+        local name="$2"
+        local comment="$3"
+
+        {
+        if [ "$PRINT_HEADER" ]; then
+                PRINT_HEADER=
+                echo "# This file was automatically generated by the $0"
+                echo "# program, run by the persistent-net-generator.rules rules file."
+                echo "#"
+                echo "# You can modify it, as long as you keep each rule on a single"
+                echo "# line, and change only the value of the NAME= key."
+        fi
+
+        echo ""
+        [ "$comment" ] && echo "# $comment"
+        echo "SUBSYSTEM==\"net\", ACTION==\"add\"$match, NAME=\"$name\""
+        } >> $RULES_FILE
+}
+
+if [ -z "$INTERFACE" ]; then
+        echo "missing \$INTERFACE" >&2
+        exit 1
+fi
+
+# Prevent concurrent processes from modifying the file at the same time.
+lock_rules_file
+
+# Check if the rules file is writeable.
+choose_rules_file
+
+# the DRIVERS key is needed to not match bridges and VLAN sub-interfaces
+if [ "$MATCHADDR" ]; then
+        match="$match, DRIVERS==\"?*\", ATTR{address}==\"$MATCHADDR\""
+fi
+
+if [ "$MATCHDRV" ]; then
+        match="$match, DRIVERS==\"$MATCHDRV\""
+fi
+
+if [ "$MATCHDEVID" ]; then
+        match="$match, ATTR{dev_id}==\"$MATCHDEVID\""
+fi
+
+if [ "$MATCHID" ]; then
+        match="$match, KERNELS==\"$MATCHID\""
+fi
+
+if [ "$MATCHIFTYPE" ]; then
+        match="$match, ATTR{type}==\"$MATCHIFTYPE\""
+fi
+
+if [ -z "$match" ]; then
+        echo "missing valid match" >&2
+        unlock_rules_file
+        exit 1
+fi
+
+basename=${INTERFACE%%[0-9]*}
+match="$match, KERNEL==\"$basename*\""
+
+if [ "$INTERFACE_NAME" ]; then
+        # external tools may request a custom name
+        COMMENT="$COMMENT (custom name provided by external tool)"
+        if [ "$INTERFACE_NAME" != "$INTERFACE" ]; then
+                INTERFACE=$INTERFACE_NAME;
+                echo "INTERFACE_NEW=$INTERFACE"
+        fi
+else
+        # if a rule using the current name already exists, find a new name
+        if interface_name_taken; then
+                INTERFACE="$basename$(find_next_available "$basename[0-9]*")"
+                # prevent INTERFACE from being "eth" instead of "eth0"
+                [ "$INTERFACE" = "${INTERFACE%%[ \[\]0-9]*}" ] && INTERFACE=${INTERFACE}0
+                echo "INTERFACE_NEW=$INTERFACE"
+        fi
+fi
+
+write_rule "$match" "$INTERFACE" "$COMMENT"
+
+unlock_rules_file
+
+exit 0
diff --git a/src/scsi_id/.gitignore b/src/scsi_id/.gitignore
new file mode 100644
index 0000000..6aebddd
--- /dev/null
+++ b/src/scsi_id/.gitignore
@@ -0,0 +1 @@
+scsi_id_version.h
diff --git a/src/scsi_id/README b/src/scsi_id/README
new file mode 100644
index 0000000..9cfe739
--- /dev/null
+++ b/src/scsi_id/README
@@ -0,0 +1,4 @@
+scsi_id - generate a SCSI unique identifier for a given SCSI device
+
+Please send questions, comments or patches to <patmans@us.ibm.com> or
+<linux-hotplug-devel@lists.sourceforge.net>.
diff --git a/src/scsi_id/scsi.h b/src/scsi_id/scsi.h
new file mode 100644
index 0000000..c423cac
--- /dev/null
+++ b/src/scsi_id/scsi.h
@@ -0,0 +1,97 @@
+/*
+ * scsi.h
+ *
+ * General scsi and linux scsi specific defines and structs.
+ *
+ * Copyright (C) IBM Corp. 2003
+ *
+ *        This program is free software; you can redistribute it and/or modify it
+ *        under the terms of the GNU General Public License as published by the
+ *        Free Software Foundation version 2 of the License.
+ */
+
+#include <scsi/scsi.h>
+
+struct scsi_ioctl_command {
+        unsigned int inlen;        /* excluding scsi command length */
+        unsigned int outlen;
+        unsigned char data[1];
+        /* on input, scsi command starts here then opt. data */
+};
+
+/*
+ * Default 5 second timeout
+ */
+#define DEF_TIMEOUT        5000
+
+#define SENSE_BUFF_LEN        32
+
+/*
+ * The request buffer size passed to the SCSI INQUIRY commands, use 254,
+ * as this is a nice value for some devices, especially some of the usb
+ * mass storage devices.
+ */
+#define        SCSI_INQ_BUFF_LEN        254
+
+/*
+ * SCSI INQUIRY vendor and model (really product) lengths.
+ */
+#define VENDOR_LENGTH        8
+#define        MODEL_LENGTH        16
+
+#define INQUIRY_CMD     0x12
+#define INQUIRY_CMDLEN  6
+
+/*
+ * INQUIRY VPD page 0x83 identifier descriptor related values. Reference the
+ * SCSI Primary Commands specification for details.
+ */
+
+/*
+ * id type values of id descriptors. These are assumed to fit in 4 bits.
+ */
+#define SCSI_ID_VENDOR_SPECIFIC        0
+#define SCSI_ID_T10_VENDOR        1
+#define SCSI_ID_EUI_64                2
+#define SCSI_ID_NAA                3
+#define SCSI_ID_RELPORT                4
+#define SCSI_ID_TGTGROUP        5
+#define SCSI_ID_LUNGROUP        6
+#define SCSI_ID_MD5                7
+#define SCSI_ID_NAME                8
+
+/*
+ * Supported NAA values. These fit in 4 bits, so the "don't care" value
+ * cannot conflict with real values.
+ */
+#define        SCSI_ID_NAA_DONT_CARE                0xff
+#define        SCSI_ID_NAA_IEEE_REG                5
+#define        SCSI_ID_NAA_IEEE_REG_EXTENDED        6
+
+/*
+ * Supported Code Set values.
+ */
+#define        SCSI_ID_BINARY        1
+#define        SCSI_ID_ASCII        2
+
+struct scsi_id_search_values {
+        u_char        id_type;
+        u_char        naa_type;
+        u_char        code_set;
+};
+
+/*
+ * Following are the "true" SCSI status codes. Linux has traditionally
+ * used a 1 bit right and masked version of these. So now CHECK_CONDITION
+ * and friends (in <scsi/scsi.h>) are deprecated.
+ */
+#define SCSI_CHECK_CONDITION 0x2
+#define SCSI_CONDITION_MET 0x4
+#define SCSI_BUSY 0x8
+#define SCSI_IMMEDIATE 0x10
+#define SCSI_IMMEDIATE_CONDITION_MET 0x14
+#define SCSI_RESERVATION_CONFLICT 0x18
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SCSI_TASK_SET_FULL 0x28
+#define SCSI_ACA_ACTIVE 0x30
+#define SCSI_TASK_ABORTED 0x40
diff --git a/src/scsi_id/scsi_id.c b/src/scsi_id/scsi_id.c
new file mode 100644
index 0000000..9bb0d7f
--- /dev/null
+++ b/src/scsi_id/scsi_id.c
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) IBM Corp. 2003
+ * Copyright (C) SUSE Linux Products GmbH, 2006
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+#include "scsi_id.h"
+
+static const struct option options[] = {
+        { "device", required_argument, NULL, 'd' },
+        { "config", required_argument, NULL, 'f' },
+        { "page", required_argument, NULL, 'p' },
+        { "blacklisted", no_argument, NULL, 'b' },
+        { "whitelisted", no_argument, NULL, 'g' },
+        { "replace-whitespace", no_argument, NULL, 'u' },
+        { "sg-version", required_argument, NULL, 's' },
+        { "verbose", no_argument, NULL, 'v' },
+        { "version", no_argument, NULL, 'V' },
+        { "export", no_argument, NULL, 'x' },
+        { "help", no_argument, NULL, 'h' },
+        {}
+};
+
+static const char short_options[] = "d:f:ghip:uvVx";
+static const char dev_short_options[] = "bgp:";
+
+static int all_good;
+static int dev_specified;
+static char config_file[MAX_PATH_LEN] = SYSCONFDIR "/scsi_id.config";
+static enum page_code default_page_code;
+static int sg_version = 4;
+static int use_stderr;
+static int debug;
+static int reformat_serial;
+static int export;
+static char vendor_str[64];
+static char model_str[64];
+static char vendor_enc_str[256];
+static char model_enc_str[256];
+static char revision_str[16];
+static char type_str[16];
+
+static void log_fn(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        vsyslog(priority, format, args);
+}
+
+static void set_type(const char *from, char *to, size_t len)
+{
+        int type_num;
+        char *eptr;
+        char *type = "generic";
+
+        type_num = strtoul(from, &eptr, 0);
+        if (eptr != from) {
+                switch (type_num) {
+                case 0:
+                        type = "disk";
+                        break;
+                case 1:
+                        type = "tape";
+                        break;
+                case 4:
+                        type = "optical";
+                        break;
+                case 5:
+                        type = "cd";
+                        break;
+                case 7:
+                        type = "optical";
+                        break;
+                case 0xe:
+                        type = "disk";
+                        break;
+                case 0xf:
+                        type = "optical";
+                        break;
+                default:
+                        break;
+                }
+        }
+        util_strscpy(to, len, type);
+}
+
+/*
+ * get_value:
+ *
+ * buf points to an '=' followed by a quoted string ("foo") or a string ending
+ * with a space or ','.
+ *
+ * Return a pointer to the NUL terminated string, returns NULL if no
+ * matches.
+ */
+static char *get_value(char **buffer)
+{
+        static char *quote_string = "\"\n";
+        static char *comma_string = ",\n";
+        char *val;
+        char *end;
+
+        if (**buffer == '"') {
+                /*
+                 * skip leading quote, terminate when quote seen
+                 */
+                (*buffer)++;
+                end = quote_string;
+        } else {
+                end = comma_string;
+        }
+        val = strsep(buffer, end);
+        if (val && end == quote_string)
+                /*
+                 * skip trailing quote
+                 */
+                (*buffer)++;
+
+        while (isspace(**buffer))
+                (*buffer)++;
+
+        return val;
+}
+
+static int argc_count(char *opts)
+{
+        int i = 0;
+        while (*opts != '\0')
+                if (*opts++ == ' ')
+                        i++;
+        return i;
+}
+
+/*
+ * get_file_options:
+ *
+ * If vendor == NULL, find a line in the config file with only "OPTIONS=";
+ * if vendor and model are set find the first OPTIONS line in the config
+ * file that matches. Set argc and argv to match the OPTIONS string.
+ *
+ * vendor and model can end in '\n'.
+ */
+static int get_file_options(struct udev *udev,
+                            const char *vendor, const char *model,
+                            int *argc, char ***newargv)
+{
+        char *buffer;
+        FILE *fd;
+        char *buf;
+        char *str1;
+        char *vendor_in, *model_in, *options_in; /* read in from file */
+        int lineno;
+        int c;
+        int retval = 0;
+
+        dbg(udev, "vendor='%s'; model='%s'\n", vendor, model);
+        fd = fopen(config_file, "r");
+        if (fd == NULL) {
+                dbg(udev, "can't open %s\n", config_file);
+                if (errno == ENOENT) {
+                        return 1;
+                } else {
+                        err(udev, "can't open %s: %s\n", config_file, strerror(errno));
+                        return -1;
+                }
+        }
+
+        /*
+         * Allocate a buffer rather than put it on the stack so we can
+         * keep it around to parse any options (any allocated newargv
+         * points into this buffer for its strings).
+         */
+        buffer = malloc(MAX_BUFFER_LEN);
+        if (!buffer) {
+                fclose(fd);
+                err(udev, "can't allocate memory\n");
+                return -1;
+        }
+
+        *newargv = NULL;
+        lineno = 0;
+        while (1) {
+                vendor_in = model_in = options_in = NULL;
+
+                buf = fgets(buffer, MAX_BUFFER_LEN, fd);
+                if (buf == NULL)
+                        break;
+                lineno++;
+                if (buf[strlen(buffer) - 1] != '\n') {
+                        err(udev, "Config file line %d too long\n", lineno);
+                        break;
+                }
+
+                while (isspace(*buf))
+                        buf++;
+
+                /* blank or all whitespace line */
+                if (*buf == '\0')
+                        continue;
+
+                /* comment line */
+                if (*buf == '#')
+                        continue;
+
+                dbg(udev, "lineno %d: '%s'\n", lineno, buf);
+                str1 = strsep(&buf, "=");
+                if (str1 && strcasecmp(str1, "VENDOR") == 0) {
+                        str1 = get_value(&buf);
+                        if (!str1) {
+                                retval = -1;
+                                break;
+                        }
+                        vendor_in = str1;
+
+                        str1 = strsep(&buf, "=");
+                        if (str1 && strcasecmp(str1, "MODEL") == 0) {
+                                str1 = get_value(&buf);
+                                if (!str1) {
+                                        retval = -1;
+                                        break;
+                                }
+                                model_in = str1;
+                                str1 = strsep(&buf, "=");
+                        }
+                }
+
+                if (str1 && strcasecmp(str1, "OPTIONS") == 0) {
+                        str1 = get_value(&buf);
+                        if (!str1) {
+                                retval = -1;
+                                break;
+                        }
+                        options_in = str1;
+                }
+                dbg(udev, "config file line %d:\n"
+                        " vendor '%s'; model '%s'; options '%s'\n",
+                        lineno, vendor_in, model_in, options_in);
+                /*
+                 * Only allow: [vendor=foo[,model=bar]]options=stuff
+                 */
+                if (!options_in || (!vendor_in && model_in)) {
+                        err(udev, "Error parsing config file line %d '%s'\n", lineno, buffer);
+                        retval = -1;
+                        break;
+                }
+                if (vendor == NULL) {
+                        if (vendor_in == NULL) {
+                                dbg(udev, "matched global option\n");
+                                break;
+                        }
+                } else if ((vendor_in && strncmp(vendor, vendor_in,
+                                                 strlen(vendor_in)) == 0) &&
+                           (!model_in || (strncmp(model, model_in,
+                                                  strlen(model_in)) == 0))) {
+                                /*
+                                 * Matched vendor and optionally model.
+                                 *
+                                 * Note: a short vendor_in or model_in can
+                                 * give a partial match (that is FOO
+                                 * matches FOOBAR).
+                                 */
+                                dbg(udev, "matched vendor/model\n");
+                                break;
+                } else {
+                        dbg(udev, "no match\n");
+                }
+        }
+
+        if (retval == 0) {
+                if (vendor_in != NULL || model_in != NULL ||
+                    options_in != NULL) {
+                        /*
+                         * Something matched. Allocate newargv, and store
+                         * values found in options_in.
+                         */
+                        strcpy(buffer, options_in);
+                        c = argc_count(buffer) + 2;
+                        *newargv = calloc(c, sizeof(**newargv));
+                        if (!*newargv) {
+                                err(udev, "can't allocate memory\n");
+                                retval = -1;
+                        } else {
+                                *argc = c;
+                                c = 0;
+                                /*
+                                 * argv[0] at 0 is skipped by getopt, but
+                                 * store the buffer address there for
+                                 * later freeing
+                                 */
+                                (*newargv)[c] = buffer;
+                                for (c = 1; c < *argc; c++)
+                                        (*newargv)[c] = strsep(&buffer, " \t");
+                        }
+                } else {
+                        /* No matches  */
+                        retval = 1;
+                }
+        }
+        if (retval != 0)
+                free(buffer);
+        fclose(fd);
+        return retval;
+}
+
+static int set_options(struct udev *udev,
+                       int argc, char **argv, const char *short_opts,
+                       char *maj_min_dev)
+{
+        int option;
+
+        /*
+         * optind is a global extern used by getopt. Since we can call
+         * set_options twice (once for command line, and once for config
+         * file) we have to reset this back to 1.
+         */
+        optind = 1;
+        while (1) {
+                option = getopt_long(argc, argv, short_opts, options, NULL);
+                if (option == -1)
+                        break;
+
+                if (optarg)
+                        dbg(udev, "option '%c' arg '%s'\n", option, optarg);
+                else
+                        dbg(udev, "option '%c'\n", option);
+
+                switch (option) {
+                case 'b':
+                        all_good = 0;
+                        break;
+
+                case 'd':
+                        dev_specified = 1;
+                        util_strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
+                        break;
+
+                case 'e':
+                        use_stderr = 1;
+                        break;
+
+                case 'f':
+                        util_strscpy(config_file, MAX_PATH_LEN, optarg);
+                        break;
+
+                case 'g':
+                        all_good = 1;
+                        break;
+
+                case 'h':
+                        printf("Usage: scsi_id OPTIONS <device>\n"
+                               "  --device=                     device node for SG_IO commands\n"
+                               "  --config=                     location of config file\n"
+                               "  --page=0x80|0x83|pre-spc3-83  SCSI page (0x80, 0x83, pre-spc3-83)\n"
+                               "  --sg-version=3|4              use SGv3 or SGv4\n"
+                               "  --blacklisted                 threat device as blacklisted\n"
+                               "  --whitelisted                 threat device as whitelisted\n"
+                               "  --replace-whitespace          replace all whitespaces by underscores\n"
+                               "  --verbose                     verbose logging\n"
+                               "  --version                     print version\n"
+                               "  --export                      print values as environment keys\n"
+                               "  --help                        print this help text\n\n");
+                        exit(0);
+
+                case 'p':
+                        if (strcmp(optarg, "0x80") == 0) {
+                                default_page_code = PAGE_80;
+                        } else if (strcmp(optarg, "0x83") == 0) {
+                                default_page_code = PAGE_83;
+                        } else if (strcmp(optarg, "pre-spc3-83") == 0) {
+                                default_page_code = PAGE_83_PRE_SPC3;
+                        } else {
+                                err(udev, "Unknown page code '%s'\n", optarg);
+                                return -1;
+                        }
+                        break;
+
+                case 's':
+                        sg_version = atoi(optarg);
+                        if (sg_version < 3 || sg_version > 4) {
+                                err(udev, "Unknown SG version '%s'\n", optarg);
+                                return -1;
+                        }
+                        break;
+
+                case 'u':
+                        reformat_serial = 1;
+                        break;
+
+                case 'x':
+                        export = 1;
+                        break;
+
+                case 'v':
+                        debug++;
+                        break;
+
+                case 'V':
+                        printf("%s\n", VERSION);
+                        exit(0);
+                        break;
+
+                default:
+                        exit(1);
+                }
+        }
+        if (optind < argc && !dev_specified) {
+                dev_specified = 1;
+                util_strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
+        }
+        return 0;
+}
+
+static int per_dev_options(struct udev *udev,
+                           struct scsi_id_device *dev_scsi, int *good_bad, int *page_code)
+{
+        int retval;
+        int newargc;
+        char **newargv = NULL;
+        int option;
+
+        *good_bad = all_good;
+        *page_code = default_page_code;
+
+        retval = get_file_options(udev, vendor_str, model_str, &newargc, &newargv);
+
+        optind = 1; /* reset this global extern */
+        while (retval == 0) {
+                option = getopt_long(newargc, newargv, dev_short_options, options, NULL);
+                if (option == -1)
+                        break;
+
+                if (optarg)
+                        dbg(udev, "option '%c' arg '%s'\n", option, optarg);
+                else
+                        dbg(udev, "option '%c'\n", option);
+
+                switch (option) {
+                case 'b':
+                        *good_bad = 0;
+                        break;
+
+                case 'g':
+                        *good_bad = 1;
+                        break;
+
+                case 'p':
+                        if (strcmp(optarg, "0x80") == 0) {
+                                *page_code = PAGE_80;
+                        } else if (strcmp(optarg, "0x83") == 0) {
+                                *page_code = PAGE_83;
+                        } else if (strcmp(optarg, "pre-spc3-83") == 0) {
+                                *page_code = PAGE_83_PRE_SPC3;
+                        } else {
+                                err(udev, "Unknown page code '%s'\n", optarg);
+                                retval = -1;
+                        }
+                        break;
+
+                default:
+                        err(udev, "Unknown or bad option '%c' (0x%x)\n", option, option);
+                        retval = -1;
+                        break;
+                }
+        }
+
+        if (newargv) {
+                free(newargv[0]);
+                free(newargv);
+        }
+        return retval;
+}
+
+static int set_inq_values(struct udev *udev, struct scsi_id_device *dev_scsi, const char *path)
+{
+        int retval;
+
+        dev_scsi->use_sg = sg_version;
+
+        retval = scsi_std_inquiry(udev, dev_scsi, path);
+        if (retval)
+                return retval;
+
+        udev_util_encode_string(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str));
+        udev_util_encode_string(dev_scsi->model, model_enc_str, sizeof(model_enc_str));
+
+        util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str));
+        util_replace_chars(vendor_str, NULL);
+        util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str));
+        util_replace_chars(model_str, NULL);
+        set_type(dev_scsi->type, type_str, sizeof(type_str));
+        util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str));
+        util_replace_chars(revision_str, NULL);
+        return 0;
+}
+
+/*
+ * scsi_id: try to get an id, if one is found, printf it to stdout.
+ * returns a value passed to exit() - 0 if printed an id, else 1.
+ */
+static int scsi_id(struct udev *udev, char *maj_min_dev)
+{
+        struct scsi_id_device dev_scsi;
+        int good_dev;
+        int page_code;
+        int retval = 0;
+
+        memset(&dev_scsi, 0x00, sizeof(struct scsi_id_device));
+
+        if (set_inq_values(udev, &dev_scsi, maj_min_dev) < 0) {
+                retval = 1;
+                goto out;
+        }
+
+        /* get per device (vendor + model) options from the config file */
+        per_dev_options(udev, &dev_scsi, &good_dev, &page_code);
+        dbg(udev, "per dev options: good %d; page code 0x%x\n", good_dev, page_code);
+        if (!good_dev) {
+                retval = 1;
+                goto out;
+        }
+
+        /* read serial number from mode pages (no values for optical drives) */
+        scsi_get_serial(udev, &dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
+
+        if (export) {
+                char serial_str[MAX_SERIAL_LEN];
+
+                printf("ID_SCSI=1\n");
+                printf("ID_VENDOR=%s\n", vendor_str);
+                printf("ID_VENDOR_ENC=%s\n", vendor_enc_str);
+                printf("ID_MODEL=%s\n", model_str);
+                printf("ID_MODEL_ENC=%s\n", model_enc_str);
+                printf("ID_REVISION=%s\n", revision_str);
+                printf("ID_TYPE=%s\n", type_str);
+                if (dev_scsi.serial[0] != '\0') {
+                        util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
+                        util_replace_chars(serial_str, NULL);
+                        printf("ID_SERIAL=%s\n", serial_str);
+                        util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str));
+                        util_replace_chars(serial_str, NULL);
+                        printf("ID_SERIAL_SHORT=%s\n", serial_str);
+                }
+                if (dev_scsi.wwn[0] != '\0') {
+                        printf("ID_WWN=0x%s\n", dev_scsi.wwn);
+                        if (dev_scsi.wwn_vendor_extension[0] != '\0') {
+                                printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
+                                printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
+                        } else {
+                                printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
+                        }
+                }
+                if (dev_scsi.tgpt_group[0] != '\0') {
+                        printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
+                }
+                if (dev_scsi.unit_serial_number[0] != '\0') {
+                        printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
+                }
+                goto out;
+        }
+
+        if (dev_scsi.serial[0] == '\0') {
+                retval = 1;
+                goto out;
+        }
+
+        if (reformat_serial) {
+                char serial_str[MAX_SERIAL_LEN];
+
+                util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
+                util_replace_chars(serial_str, NULL);
+                printf("%s\n", serial_str);
+                goto out;
+        }
+
+        printf("%s\n", dev_scsi.serial);
+out:
+        return retval;
+}
+
+int main(int argc, char **argv)
+{
+        struct udev *udev;
+        int retval = 0;
+        char maj_min_dev[MAX_PATH_LEN];
+        int newargc;
+        char **newargv;
+
+        udev = udev_new();
+        if (udev == NULL)
+                goto exit;
+
+        udev_log_init("scsi_id");
+        udev_set_log_fn(udev, log_fn);
+
+        /*
+         * Get config file options.
+         */
+        newargv = NULL;
+        retval = get_file_options(udev, NULL, NULL, &newargc, &newargv);
+        if (retval < 0) {
+                retval = 1;
+                goto exit;
+        }
+        if (newargv && (retval == 0)) {
+                if (set_options(udev, newargc, newargv, short_options, maj_min_dev) < 0) {
+                        retval = 2;
+                        goto exit;
+                }
+                free(newargv);
+        }
+
+        /*
+         * Get command line options (overriding any config file settings).
+         */
+        if (set_options(udev, argc, argv, short_options, maj_min_dev) < 0)
+                exit(1);
+
+        if (!dev_specified) {
+                err(udev, "no device specified\n");
+                retval = 1;
+                goto exit;
+        }
+
+        retval = scsi_id(udev, maj_min_dev);
+
+exit:
+        udev_unref(udev);
+        udev_log_close();
+        return retval;
+}
diff --git a/src/scsi_id/scsi_id.h b/src/scsi_id/scsi_id.h
new file mode 100644
index 0000000..828a983
--- /dev/null
+++ b/src/scsi_id/scsi_id.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) IBM Corp. 2003
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define        MAX_PATH_LEN        512
+
+/*
+ * MAX_ATTR_LEN: maximum length of the result of reading a sysfs
+ * attribute.
+ */
+#define        MAX_ATTR_LEN        256
+
+/*
+ * MAX_SERIAL_LEN: the maximum length of the serial number, including
+ * added prefixes such as vendor and product (model) strings.
+ */
+#define        MAX_SERIAL_LEN        256
+
+/*
+ * MAX_BUFFER_LEN: maximum buffer size and line length used while reading
+ * the config file.
+ */
+#define MAX_BUFFER_LEN        256
+
+struct scsi_id_device {
+        char vendor[9];
+        char model[17];
+        char revision[5];
+        char type[33];
+        char kernel[64];
+        char serial[MAX_SERIAL_LEN];
+        char serial_short[MAX_SERIAL_LEN];
+        int use_sg;
+
+        /* Always from page 0x80 e.g. 'B3G1P8500RWT' - may not be unique */
+        char unit_serial_number[MAX_SERIAL_LEN];
+
+        /* NULs if not set - otherwise hex encoding using lower-case e.g. '50014ee0016eb572' */
+        char wwn[17];
+
+        /* NULs if not set - otherwise hex encoding using lower-case e.g. '0xe00000d80000' */
+        char wwn_vendor_extension[17];
+
+        /* NULs if not set - otherwise decimal number */
+        char tgpt_group[8];
+};
+
+extern int scsi_std_inquiry(struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname);
+extern int scsi_get_serial (struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname,
+                            int page_code, int len);
+
+/*
+ * Page code values.
+ */
+enum page_code {
+                PAGE_83_PRE_SPC3 = -0x83,
+                PAGE_UNSPECIFIED = 0x00,
+                PAGE_80                 = 0x80,
+                PAGE_83                 = 0x83,
+};
diff --git a/src/scsi_id/scsi_serial.c b/src/scsi_id/scsi_serial.c
new file mode 100644
index 0000000..f1d63f4
--- /dev/null
+++ b/src/scsi_id/scsi_serial.c
@@ -0,0 +1,990 @@
+/*
+ * Copyright (C) IBM Corp. 2003
+ *
+ * Author: Patrick Mansfield<patmans@us.ibm.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <time.h>
+#include <inttypes.h>
+#include <scsi/scsi.h>
+#include <scsi/sg.h>
+#include <linux/types.h>
+#include <linux/bsg.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+#include "scsi.h"
+#include "scsi_id.h"
+
+/*
+ * A priority based list of id, naa, and binary/ascii for the identifier
+ * descriptor in VPD page 0x83.
+ *
+ * Brute force search for a match starting with the first value in the
+ * following id_search_list. This is not a performance issue, since there
+ * is normally one or some small number of descriptors.
+ */
+static const struct scsi_id_search_values id_search_list[] = {
+        { SCSI_ID_TGTGROUP,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_BINARY },
+        { SCSI_ID_NAA,        SCSI_ID_NAA_IEEE_REG_EXTENDED,        SCSI_ID_BINARY },
+        { SCSI_ID_NAA,        SCSI_ID_NAA_IEEE_REG_EXTENDED,        SCSI_ID_ASCII },
+        { SCSI_ID_NAA,        SCSI_ID_NAA_IEEE_REG,        SCSI_ID_BINARY },
+        { SCSI_ID_NAA,        SCSI_ID_NAA_IEEE_REG,        SCSI_ID_ASCII },
+        /*
+         * Devices already exist using NAA values that are now marked
+         * reserved. These should not conflict with other values, or it is
+         * a bug in the device. As long as we find the IEEE extended one
+         * first, we really don't care what other ones are used. Using
+         * don't care here means that a device that returns multiple
+         * non-IEEE descriptors in a random order will get different
+         * names.
+         */
+        { SCSI_ID_NAA,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_BINARY },
+        { SCSI_ID_NAA,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_ASCII },
+        { SCSI_ID_EUI_64,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_BINARY },
+        { SCSI_ID_EUI_64,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_ASCII },
+        { SCSI_ID_T10_VENDOR,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_BINARY },
+        { SCSI_ID_T10_VENDOR,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_ASCII },
+        { SCSI_ID_VENDOR_SPECIFIC,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_BINARY },
+        { SCSI_ID_VENDOR_SPECIFIC,        SCSI_ID_NAA_DONT_CARE,        SCSI_ID_ASCII },
+};
+
+static const char hex_str[]="0123456789abcdef";
+
+/*
+ * Values returned in the result/status, only the ones used by the code
+ * are used here.
+ */
+
+#define DID_NO_CONNECT                        0x01        /* Unable to connect before timeout */
+#define DID_BUS_BUSY                        0x02        /* Bus remain busy until timeout */
+#define DID_TIME_OUT                        0x03        /* Timed out for some other reason */
+#define DRIVER_TIMEOUT                        0x06
+#define DRIVER_SENSE                        0x08        /* Sense_buffer has been set */
+
+/* The following "category" function returns one of the following */
+#define SG_ERR_CAT_CLEAN                0        /* No errors or other information */
+#define SG_ERR_CAT_MEDIA_CHANGED        1        /* interpreted from sense buffer */
+#define SG_ERR_CAT_RESET                2        /* interpreted from sense buffer */
+#define SG_ERR_CAT_TIMEOUT                3
+#define SG_ERR_CAT_RECOVERED                4        /* Successful command after recovered err */
+#define SG_ERR_CAT_NOTSUPPORTED                5        /* Illegal / unsupported command */
+#define SG_ERR_CAT_SENSE                98        /* Something else in the sense buffer */
+#define SG_ERR_CAT_OTHER                99        /* Some other error/warning */
+
+static int do_scsi_page80_inquiry(struct udev *udev,
+                                  struct scsi_id_device *dev_scsi, int fd,
+                                  char *serial, char *serial_short, int max_len);
+
+static int sg_err_category_new(struct udev *udev,
+                               int scsi_status, int msg_status, int
+                               host_status, int driver_status, const
+                               unsigned char *sense_buffer, int sb_len)
+{
+        scsi_status &= 0x7e;
+
+        /*
+         * XXX change to return only two values - failed or OK.
+         */
+
+        if (!scsi_status && !host_status && !driver_status)
+                return SG_ERR_CAT_CLEAN;
+
+        if ((scsi_status == SCSI_CHECK_CONDITION) ||
+            (scsi_status == SCSI_COMMAND_TERMINATED) ||
+            ((driver_status & 0xf) == DRIVER_SENSE)) {
+                if (sense_buffer && (sb_len > 2)) {
+                        int sense_key;
+                        unsigned char asc;
+
+                        if (sense_buffer[0] & 0x2) {
+                                sense_key = sense_buffer[1] & 0xf;
+                                asc = sense_buffer[2];
+                        } else {
+                                sense_key = sense_buffer[2] & 0xf;
+                                asc = (sb_len > 12) ? sense_buffer[12] : 0;
+                        }
+
+                        if (sense_key == RECOVERED_ERROR)
+                                return SG_ERR_CAT_RECOVERED;
+                        else if (sense_key == UNIT_ATTENTION) {
+                                if (0x28 == asc)
+                                        return SG_ERR_CAT_MEDIA_CHANGED;
+                                if (0x29 == asc)
+                                        return SG_ERR_CAT_RESET;
+                        } else if (sense_key == ILLEGAL_REQUEST) {
+                                return SG_ERR_CAT_NOTSUPPORTED;
+                        }
+                }
+                return SG_ERR_CAT_SENSE;
+        }
+        if (host_status) {
+                if ((host_status == DID_NO_CONNECT) ||
+                    (host_status == DID_BUS_BUSY) ||
+                    (host_status == DID_TIME_OUT))
+                        return SG_ERR_CAT_TIMEOUT;
+        }
+        if (driver_status) {
+                if (driver_status == DRIVER_TIMEOUT)
+                        return SG_ERR_CAT_TIMEOUT;
+        }
+        return SG_ERR_CAT_OTHER;
+}
+
+static int sg_err_category3(struct udev *udev, struct sg_io_hdr *hp)
+{
+        return sg_err_category_new(udev,
+                                   hp->status, hp->msg_status,
+                                   hp->host_status, hp->driver_status,
+                                   hp->sbp, hp->sb_len_wr);
+}
+
+static int sg_err_category4(struct udev *udev, struct sg_io_v4 *hp)
+{
+        return sg_err_category_new(udev, hp->device_status, 0,
+                                   hp->transport_status, hp->driver_status,
+                                   (unsigned char *)(uintptr_t)hp->response,
+                                   hp->response_len);
+}
+
+static int scsi_dump_sense(struct udev *udev,
+                           struct scsi_id_device *dev_scsi,
+                           unsigned char *sense_buffer, int sb_len)
+{
+        int s;
+        int code;
+        int sense_class;
+        int sense_key;
+        int asc, ascq;
+#ifdef DUMP_SENSE
+        char out_buffer[256];
+        int i, j;
+#endif
+
+        /*
+         * Figure out and print the sense key, asc and ascq.
+         *
+         * If you want to suppress these for a particular drive model, add
+         * a black list entry in the scsi_id config file.
+         *
+         * XXX We probably need to: lookup the sense/asc/ascq in a retry
+         * table, and if found return 1 (after dumping the sense, asc, and
+         * ascq). So, if/when we get something like a power on/reset,
+         * we'll retry the command.
+         */
+
+        dbg(udev, "got check condition\n");
+
+        if (sb_len < 1) {
+                info(udev, "%s: sense buffer empty\n", dev_scsi->kernel);
+                return -1;
+        }
+
+        sense_class = (sense_buffer[0] >> 4) & 0x07;
+        code = sense_buffer[0] & 0xf;
+
+        if (sense_class == 7) {
+                /*
+                 * extended sense data.
+                 */
+                s = sense_buffer[7] + 8;
+                if (sb_len < s) {
+                        info(udev, "%s: sense buffer too small %d bytes, %d bytes too short\n",
+                            dev_scsi->kernel, sb_len, s - sb_len);
+                        return -1;
+                }
+                if ((code == 0x0) || (code == 0x1)) {
+                        sense_key = sense_buffer[2] & 0xf;
+                        if (s < 14) {
+                                /*
+                                 * Possible?
+                                 */
+                                info(udev, "%s: sense result too" " small %d bytes\n",
+                                    dev_scsi->kernel, s);
+                                return -1;
+                        }
+                        asc = sense_buffer[12];
+                        ascq = sense_buffer[13];
+                } else if ((code == 0x2) || (code == 0x3)) {
+                        sense_key = sense_buffer[1] & 0xf;
+                        asc = sense_buffer[2];
+                        ascq = sense_buffer[3];
+                } else {
+                        info(udev, "%s: invalid sense code 0x%x\n",
+                            dev_scsi->kernel, code);
+                        return -1;
+                }
+                info(udev, "%s: sense key 0x%x ASC 0x%x ASCQ 0x%x\n",
+                    dev_scsi->kernel, sense_key, asc, ascq);
+        } else {
+                if (sb_len < 4) {
+                        info(udev, "%s: sense buffer too small %d bytes, %d bytes too short\n",
+                            dev_scsi->kernel, sb_len, 4 - sb_len);
+                        return -1;
+                }
+
+                if (sense_buffer[0] < 15)
+                        info(udev, "%s: old sense key: 0x%x\n", dev_scsi->kernel, sense_buffer[0] & 0x0f);
+                else
+                        info(udev, "%s: sense = %2x %2x\n",
+                            dev_scsi->kernel, sense_buffer[0], sense_buffer[2]);
+                info(udev, "%s: non-extended sense class %d code 0x%0x\n",
+                    dev_scsi->kernel, sense_class, code);
+
+        }
+
+#ifdef DUMP_SENSE
+        for (i = 0, j = 0; (i < s) && (j < 254); i++) {
+                dbg(udev, "i %d, j %d\n", i, j);
+                out_buffer[j++] = hex_str[(sense_buffer[i] & 0xf0) >> 4];
+                out_buffer[j++] = hex_str[sense_buffer[i] & 0x0f];
+                out_buffer[j++] = ' ';
+        }
+        out_buffer[j] = '\0';
+        info(udev, "%s: sense dump:\n", dev_scsi->kernel);
+        info(udev, "%s: %s\n", dev_scsi->kernel, out_buffer);
+
+#endif
+        return -1;
+}
+
+static int scsi_dump(struct udev *udev,
+                     struct scsi_id_device *dev_scsi, struct sg_io_hdr *io)
+{
+        if (!io->status && !io->host_status && !io->msg_status &&
+            !io->driver_status) {
+                /*
+                 * Impossible, should not be called.
+                 */
+                info(udev, "%s: called with no error\n", __FUNCTION__);
+                return -1;
+        }
+
+        info(udev, "%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x\n",
+            dev_scsi->kernel, io->driver_status, io->host_status, io->msg_status, io->status);
+        if (io->status == SCSI_CHECK_CONDITION)
+                return scsi_dump_sense(udev, dev_scsi, io->sbp, io->sb_len_wr);
+        else
+                return -1;
+}
+
+static int scsi_dump_v4(struct udev *udev,
+                        struct scsi_id_device *dev_scsi, struct sg_io_v4 *io)
+{
+        if (!io->device_status && !io->transport_status &&
+            !io->driver_status) {
+                /*
+                 * Impossible, should not be called.
+                 */
+                info(udev, "%s: called with no error\n", __FUNCTION__);
+                return -1;
+        }
+
+        info(udev, "%s: sg_io failed status 0x%x 0x%x 0x%x\n",
+            dev_scsi->kernel, io->driver_status, io->transport_status,
+             io->device_status);
+        if (io->device_status == SCSI_CHECK_CONDITION)
+                return scsi_dump_sense(udev, dev_scsi, (unsigned char *)(uintptr_t)io->response,
+                                       io->response_len);
+        else
+                return -1;
+}
+
+static int scsi_inquiry(struct udev *udev,
+                        struct scsi_id_device *dev_scsi, int fd,
+                        unsigned char evpd, unsigned char page,
+                        unsigned char *buf, unsigned int buflen)
+{
+        unsigned char inq_cmd[INQUIRY_CMDLEN] =
+                { INQUIRY_CMD, evpd, page, 0, buflen, 0 };
+        unsigned char sense[SENSE_BUFF_LEN];
+        void *io_buf;
+        struct sg_io_v4 io_v4;
+        struct sg_io_hdr io_hdr;
+        int retry = 3; /* rather random */
+        int retval;
+
+        if (buflen > SCSI_INQ_BUFF_LEN) {
+                info(udev, "buflen %d too long\n", buflen);
+                return -1;
+        }
+
+resend:
+        dbg(udev, "%s evpd %d, page 0x%x\n", dev_scsi->kernel, evpd, page);
+
+        if (dev_scsi->use_sg == 4) {
+                memset(&io_v4, 0, sizeof(struct sg_io_v4));
+                io_v4.guard = 'Q';
+                io_v4.protocol = BSG_PROTOCOL_SCSI;
+                io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+                io_v4.request_len = sizeof(inq_cmd);
+                io_v4.request = (uintptr_t)inq_cmd;
+                io_v4.max_response_len = sizeof(sense);
+                io_v4.response = (uintptr_t)sense;
+                io_v4.din_xfer_len = buflen;
+                io_v4.din_xferp = (uintptr_t)buf;
+                io_buf = (void *)&io_v4;
+        } else {
+                memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+                io_hdr.interface_id = 'S';
+                io_hdr.cmd_len = sizeof(inq_cmd);
+                io_hdr.mx_sb_len = sizeof(sense);
+                io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+                io_hdr.dxfer_len = buflen;
+                io_hdr.dxferp = buf;
+                io_hdr.cmdp = inq_cmd;
+                io_hdr.sbp = sense;
+                io_hdr.timeout = DEF_TIMEOUT;
+                io_buf = (void *)&io_hdr;
+        }
+
+        retval = ioctl(fd, SG_IO, io_buf);
+        if (retval < 0) {
+                if ((errno == EINVAL || errno == ENOSYS) && dev_scsi->use_sg == 4) {
+                        dev_scsi->use_sg = 3;
+                        goto resend;
+                }
+                info(udev, "%s: ioctl failed: %s\n", dev_scsi->kernel, strerror(errno));
+                goto error;
+        }
+
+        if (dev_scsi->use_sg == 4)
+                retval = sg_err_category4(udev, io_buf);
+        else
+                retval = sg_err_category3(udev, io_buf);
+
+        switch (retval) {
+                case SG_ERR_CAT_NOTSUPPORTED:
+                        buf[1] = 0;
+                        /* Fallthrough */
+                case SG_ERR_CAT_CLEAN:
+                case SG_ERR_CAT_RECOVERED:
+                        retval = 0;
+                        break;
+
+                default:
+                        if (dev_scsi->use_sg == 4)
+                                retval = scsi_dump_v4(udev, dev_scsi, io_buf);
+                        else
+                                retval = scsi_dump(udev, dev_scsi, io_buf);
+        }
+
+        if (!retval) {
+                retval = buflen;
+        } else if (retval > 0) {
+                if (--retry > 0) {
+                        dbg(udev, "%s: Retrying ...\n", dev_scsi->kernel);
+                        goto resend;
+                }
+                retval = -1;
+        }
+
+error:
+        if (retval < 0)
+                info(udev, "%s: Unable to get INQUIRY vpd %d page 0x%x.\n",
+                    dev_scsi->kernel, evpd, page);
+
+        return retval;
+}
+
+/* Get list of supported EVPD pages */
+static int do_scsi_page0_inquiry(struct udev *udev,
+                                 struct scsi_id_device *dev_scsi, int fd,
+                                 unsigned char *buffer, unsigned int len)
+{
+        int retval;
+
+        memset(buffer, 0, len);
+        retval = scsi_inquiry(udev, dev_scsi, fd, 1, 0x0, buffer, len);
+        if (retval < 0)
+                return 1;
+
+        if (buffer[1] != 0) {
+                info(udev, "%s: page 0 not available.\n", dev_scsi->kernel);
+                return 1;
+        }
+        if (buffer[3] > len) {
+                info(udev, "%s: page 0 buffer too long %d\n", dev_scsi->kernel,         buffer[3]);
+                return 1;
+        }
+
+        /*
+         * Following check is based on code once included in the 2.5.x
+         * kernel.
+         *
+         * Some ill behaved devices return the standard inquiry here
+         * rather than the evpd data, snoop the data to verify.
+         */
+        if (buffer[3] > MODEL_LENGTH) {
+                /*
+                 * If the vendor id appears in the page assume the page is
+                 * invalid.
+                 */
+                if (!strncmp((char *)&buffer[VENDOR_LENGTH], dev_scsi->vendor, VENDOR_LENGTH)) {
+                        info(udev, "%s: invalid page0 data\n", dev_scsi->kernel);
+                        return 1;
+                }
+        }
+        return 0;
+}
+
+/*
+ * The caller checks that serial is long enough to include the vendor +
+ * model.
+ */
+static int prepend_vendor_model(struct udev *udev,
+                                struct scsi_id_device *dev_scsi, char *serial)
+{
+        int ind;
+
+        strncpy(serial, dev_scsi->vendor, VENDOR_LENGTH);
+        strncat(serial, dev_scsi->model, MODEL_LENGTH);
+        ind = strlen(serial);
+
+        /*
+         * This is not a complete check, since we are using strncat/cpy
+         * above, ind will never be too large.
+         */
+        if (ind != (VENDOR_LENGTH + MODEL_LENGTH)) {
+                info(udev, "%s: expected length %d, got length %d\n",
+                     dev_scsi->kernel, (VENDOR_LENGTH + MODEL_LENGTH), ind);
+                return -1;
+        }
+        return ind;
+}
+
+/**
+ * check_fill_0x83_id - check the page 0x83 id, if OK allocate and fill
+ * serial number.
+ **/
+static int check_fill_0x83_id(struct udev *udev,
+                              struct scsi_id_device *dev_scsi,
+                              unsigned char *page_83,
+                              const struct scsi_id_search_values
+                              *id_search, char *serial, char *serial_short,
+                              int max_len, char *wwn,
+                              char *wwn_vendor_extension, char *tgpt_group)
+{
+        int i, j, s, len;
+
+        /*
+         * ASSOCIATION must be with the device (value 0)
+         * or with the target port for SCSI_ID_TGTPORT
+         */
+        if ((page_83[1] & 0x30) == 0x10) {
+                if (id_search->id_type != SCSI_ID_TGTGROUP)
+                        return 1;
+        } else if ((page_83[1] & 0x30) != 0) {
+                return 1;
+        }
+
+        if ((page_83[1] & 0x0f) != id_search->id_type)
+                return 1;
+
+        /*
+         * Possibly check NAA sub-type.
+         */
+        if ((id_search->naa_type != SCSI_ID_NAA_DONT_CARE) &&
+            (id_search->naa_type != (page_83[4] & 0xf0) >> 4))
+                return 1;
+
+        /*
+         * Check for matching code set - ASCII or BINARY.
+         */
+        if ((page_83[0] & 0x0f) != id_search->code_set)
+                return 1;
+
+        /*
+         * page_83[3]: identifier length
+         */
+        len = page_83[3];
+        if ((page_83[0] & 0x0f) != SCSI_ID_ASCII)
+                /*
+                 * If not ASCII, use two bytes for each binary value.
+                 */
+                len *= 2;
+
+        /*
+         * Add one byte for the NUL termination, and one for the id_type.
+         */
+        len += 2;
+        if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
+                len += VENDOR_LENGTH + MODEL_LENGTH;
+
+        if (max_len < len) {
+                info(udev, "%s: length %d too short - need %d\n",
+                    dev_scsi->kernel, max_len, len);
+                return 1;
+        }
+
+        if (id_search->id_type == SCSI_ID_TGTGROUP && tgpt_group != NULL) {
+                unsigned int group;
+
+                group = ((unsigned int)page_83[6] << 8) | page_83[7];
+                sprintf(tgpt_group,"%x", group);
+                return 1;
+        }
+
+        serial[0] = hex_str[id_search->id_type];
+
+        /*
+         * For SCSI_ID_VENDOR_SPECIFIC prepend the vendor and model before
+         * the id since it is not unique across all vendors and models,
+         * this differs from SCSI_ID_T10_VENDOR, where the vendor is
+         * included in the identifier.
+         */
+        if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
+                if (prepend_vendor_model(udev, dev_scsi, &serial[1]) < 0) {
+                        dbg(udev, "prepend failed\n");
+                        return 1;
+                }
+
+        i = 4; /* offset to the start of the identifier */
+        s = j = strlen(serial);
+        if ((page_83[0] & 0x0f) == SCSI_ID_ASCII) {
+                /*
+                 * ASCII descriptor.
+                 */
+                while (i < (4 + page_83[3]))
+                        serial[j++] = page_83[i++];
+        } else {
+                /*
+                 * Binary descriptor, convert to ASCII, using two bytes of
+                 * ASCII for each byte in the page_83.
+                 */
+                while (i < (4 + page_83[3])) {
+                        serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4];
+                        serial[j++] = hex_str[page_83[i] & 0x0f];
+                        i++;
+                }
+        }
+
+        strcpy(serial_short, &serial[s]);
+
+        if (id_search->id_type == SCSI_ID_NAA && wwn != NULL) {
+                strncpy(wwn, &serial[s], 16);
+                if (wwn_vendor_extension != NULL) {
+                        strncpy(wwn_vendor_extension, &serial[s + 16], 16);
+                }
+        }
+
+        return 0;
+}
+
+/* Extract the raw binary from VPD 0x83 pre-SPC devices */
+static int check_fill_0x83_prespc3(struct udev *udev,
+                                   struct scsi_id_device *dev_scsi,
+                                   unsigned char *page_83,
+                                   const struct scsi_id_search_values
+                                   *id_search, char *serial, char *serial_short, int max_len)
+{
+        int i, j;
+
+        dbg(udev, "using pre-spc3-83 for %s\n", dev_scsi->kernel);
+        serial[0] = hex_str[id_search->id_type];
+        /* serial has been memset to zero before */
+        j = strlen(serial);        /* j = 1; */
+
+        for (i = 0; (i < page_83[3]) && (j < max_len-3); ++i) {
+                serial[j++] = hex_str[(page_83[4+i] & 0xf0) >> 4];
+                serial[j++] = hex_str[ page_83[4+i] & 0x0f];
+        }
+        serial[max_len-1] = 0;
+        strncpy(serial_short, serial, max_len-1);
+        return 0;
+}
+
+
+/* Get device identification VPD page */
+static int do_scsi_page83_inquiry(struct udev *udev,
+                                  struct scsi_id_device *dev_scsi, int fd,
+                                  char *serial, char *serial_short, int len,
+                                  char *unit_serial_number, char *wwn,
+                                  char *wwn_vendor_extension, char *tgpt_group)
+{
+        int retval;
+        unsigned int id_ind, j;
+        unsigned char page_83[SCSI_INQ_BUFF_LEN];
+
+        /* also pick up the page 80 serial number */
+        do_scsi_page80_inquiry(udev, dev_scsi, fd, NULL, unit_serial_number, MAX_SERIAL_LEN);
+
+        memset(page_83, 0, SCSI_INQ_BUFF_LEN);
+        retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_83, page_83,
+                              SCSI_INQ_BUFF_LEN);
+        if (retval < 0)
+                return 1;
+
+        if (page_83[1] != PAGE_83) {
+                info(udev, "%s: Invalid page 0x83\n", dev_scsi->kernel);
+                return 1;
+        }
+
+        /*
+         * XXX Some devices (IBM 3542) return all spaces for an identifier if
+         * the LUN is not actually configured. This leads to identifiers of
+         * the form: "1            ".
+         */
+
+        /*
+         * Model 4, 5, and (some) model 6 EMC Symmetrix devices return
+         * a page 83 reply according to SCSI-2 format instead of SPC-2/3.
+         *
+         * The SCSI-2 page 83 format returns an IEEE WWN in binary
+         * encoded hexi-decimal in the 16 bytes following the initial
+         * 4-byte page 83 reply header.
+         *
+         * Both the SPC-2 and SPC-3 formats return an IEEE WWN as part
+         * of an Identification descriptor.  The 3rd byte of the first
+         * Identification descriptor is a reserved (BSZ) byte field.
+         *
+         * Reference the 7th byte of the page 83 reply to determine
+         * whether the reply is compliant with SCSI-2 or SPC-2/3
+         * specifications.  A zero value in the 7th byte indicates
+         * an SPC-2/3 conformant reply, (i.e., the reserved field of the
+         * first Identification descriptor).  This byte will be non-zero
+         * for a SCSI-2 conformant page 83 reply from these EMC
+         * Symmetrix models since the 7th byte of the reply corresponds
+         * to the 4th and 5th nibbles of the 6-byte OUI for EMC, that is,
+         * 0x006048.
+         */
+
+        if (page_83[6] != 0)
+                return check_fill_0x83_prespc3(udev,
+                                               dev_scsi, page_83, id_search_list,
+                                               serial, serial_short, len);
+
+        /*
+         * Search for a match in the prioritized id_search_list - since WWN ids
+         * come first we can pick up the WWN in check_fill_0x83_id().
+         */
+        for (id_ind = 0;
+             id_ind < sizeof(id_search_list)/sizeof(id_search_list[0]);
+             id_ind++) {
+                /*
+                 * Examine each descriptor returned. There is normally only
+                 * one or a small number of descriptors.
+                 */
+                for (j = 4; j <= (unsigned int)page_83[3] + 3; j += page_83[j + 3] + 4) {
+                        retval = check_fill_0x83_id(udev,
+                                                    dev_scsi, &page_83[j],
+                                                    &id_search_list[id_ind],
+                                                    serial, serial_short, len,
+                                                    wwn, wwn_vendor_extension,
+                                                    tgpt_group);
+                        dbg(udev, "%s id desc %d/%d/%d\n", dev_scsi->kernel,
+                                id_search_list[id_ind].id_type,
+                                id_search_list[id_ind].naa_type,
+                                id_search_list[id_ind].code_set);
+                        if (!retval) {
+                                dbg(udev, "  used\n");
+                                return retval;
+                        } else if (retval < 0) {
+                                dbg(udev, "  failed\n");
+                                return retval;
+                        } else {
+                                dbg(udev, "  not used\n");
+                        }
+                }
+        }
+        return 1;
+}
+
+/*
+ * Get device identification VPD page for older SCSI-2 device which is not
+ * compliant with either SPC-2 or SPC-3 format.
+ *
+ * Return the hard coded error code value 2 if the page 83 reply is not
+ * conformant to the SCSI-2 format.
+ */
+static int do_scsi_page83_prespc3_inquiry(struct udev *udev,
+                                          struct scsi_id_device *dev_scsi, int fd,
+                                          char *serial, char *serial_short, int len)
+{
+        int retval;
+        int i, j;
+        unsigned char page_83[SCSI_INQ_BUFF_LEN];
+
+        memset(page_83, 0, SCSI_INQ_BUFF_LEN);
+        retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_83, page_83, SCSI_INQ_BUFF_LEN);
+        if (retval < 0)
+                return 1;
+
+        if (page_83[1] != PAGE_83) {
+                info(udev, "%s: Invalid page 0x83\n", dev_scsi->kernel);
+                return 1;
+        }
+        /*
+         * Model 4, 5, and (some) model 6 EMC Symmetrix devices return
+         * a page 83 reply according to SCSI-2 format instead of SPC-2/3.
+         *
+         * The SCSI-2 page 83 format returns an IEEE WWN in binary
+         * encoded hexi-decimal in the 16 bytes following the initial
+         * 4-byte page 83 reply header.
+         *
+         * Both the SPC-2 and SPC-3 formats return an IEEE WWN as part
+         * of an Identification descriptor.  The 3rd byte of the first
+         * Identification descriptor is a reserved (BSZ) byte field.
+         *
+         * Reference the 7th byte of the page 83 reply to determine
+         * whether the reply is compliant with SCSI-2 or SPC-2/3
+         * specifications.  A zero value in the 7th byte indicates
+         * an SPC-2/3 conformant reply, (i.e., the reserved field of the
+         * first Identification descriptor).  This byte will be non-zero
+         * for a SCSI-2 conformant page 83 reply from these EMC
+         * Symmetrix models since the 7th byte of the reply corresponds
+         * to the 4th and 5th nibbles of the 6-byte OUI for EMC, that is,
+         * 0x006048.
+         */
+        if (page_83[6] == 0)
+                return 2;
+
+        serial[0] = hex_str[id_search_list[0].id_type];
+        /*
+         * The first four bytes contain data, not a descriptor.
+         */
+        i = 4;
+        j = strlen(serial);
+        /*
+         * Binary descriptor, convert to ASCII,
+         * using two bytes of ASCII for each byte
+         * in the page_83.
+         */
+        while (i < (page_83[3]+4)) {
+                serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4];
+                serial[j++] = hex_str[page_83[i] & 0x0f];
+                i++;
+        }
+        dbg(udev, "using pre-spc3-83 for %s\n", dev_scsi->kernel);
+        return 0;
+}
+
+/* Get unit serial number VPD page */
+static int do_scsi_page80_inquiry(struct udev *udev,
+                                  struct scsi_id_device *dev_scsi, int fd,
+                                  char *serial, char *serial_short, int max_len)
+{
+        int retval;
+        int ser_ind;
+        int i;
+        int len;
+        unsigned char buf[SCSI_INQ_BUFF_LEN];
+
+        memset(buf, 0, SCSI_INQ_BUFF_LEN);
+        retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_80, buf, SCSI_INQ_BUFF_LEN);
+        if (retval < 0)
+                return retval;
+
+        if (buf[1] != PAGE_80) {
+                info(udev, "%s: Invalid page 0x80\n", dev_scsi->kernel);
+                return 1;
+        }
+
+        len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3];
+        if (max_len < len) {
+                info(udev, "%s: length %d too short - need %d\n",
+                     dev_scsi->kernel, max_len, len);
+                return 1;
+        }
+        /*
+         * Prepend 'S' to avoid unlikely collision with page 0x83 vendor
+         * specific type where we prepend '0' + vendor + model.
+         */
+        len = buf[3];
+        if (serial != NULL) {
+                serial[0] = 'S';
+                ser_ind = prepend_vendor_model(udev, dev_scsi, &serial[1]);
+                if (ser_ind < 0)
+                        return 1;
+                for (i = 4; i < len + 4; i++, ser_ind++)
+                        serial[ser_ind] = buf[i];
+        }
+        if (serial_short != NULL) {
+                memcpy(serial_short, &buf[4], len);
+                serial_short[len] = '\0';
+        }
+        return 0;
+}
+
+int scsi_std_inquiry(struct udev *udev,
+                     struct scsi_id_device *dev_scsi, const char *devname)
+{
+        int fd;
+        unsigned char buf[SCSI_INQ_BUFF_LEN];
+        struct stat statbuf;
+        int err = 0;
+
+        dbg(udev, "opening %s\n", devname);
+        fd = open(devname, O_RDONLY | O_NONBLOCK);
+        if (fd < 0) {
+                info(udev, "scsi_id: cannot open %s: %s\n",
+                     devname, strerror(errno));
+                return 1;
+        }
+
+        if (fstat(fd, &statbuf) < 0) {
+                info(udev, "scsi_id: cannot stat %s: %s\n",
+                     devname, strerror(errno));
+                err = 2;
+                goto out;
+        }
+        sprintf(dev_scsi->kernel,"%d:%d", major(statbuf.st_rdev),
+                minor(statbuf.st_rdev));
+
+        memset(buf, 0, SCSI_INQ_BUFF_LEN);
+        err = scsi_inquiry(udev, dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN);
+        if (err < 0)
+                goto out;
+
+        err = 0;
+        memcpy(dev_scsi->vendor, buf + 8, 8);
+        dev_scsi->vendor[8] = '\0';
+        memcpy(dev_scsi->model, buf + 16, 16);
+        dev_scsi->model[16] = '\0';
+        memcpy(dev_scsi->revision, buf + 32, 4);
+        dev_scsi->revision[4] = '\0';
+        sprintf(dev_scsi->type,"%x", buf[0] & 0x1f);
+
+out:
+        close(fd);
+        return err;
+}
+
+int scsi_get_serial(struct udev *udev,
+                    struct scsi_id_device *dev_scsi, const char *devname,
+                    int page_code, int len)
+{
+        unsigned char page0[SCSI_INQ_BUFF_LEN];
+        int fd = -1;
+        int cnt;
+        int ind;
+        int retval;
+
+        memset(dev_scsi->serial, 0, len);
+        dbg(udev, "opening %s\n", devname);
+        srand((unsigned int)getpid());
+        for (cnt = 20; cnt > 0; cnt--) {
+                struct timespec duration;
+
+                fd = open(devname, O_RDONLY | O_NONBLOCK);
+                if (fd >= 0 || errno != EBUSY)
+                        break;
+                duration.tv_sec = 0;
+                duration.tv_nsec = (200 * 1000 * 1000) + (rand() % 100 * 1000 * 1000);
+                nanosleep(&duration, NULL);
+        }
+        if (fd < 0)
+                return 1;
+
+        if (page_code == PAGE_80) {
+                if (do_scsi_page80_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len)) {
+                        retval = 1;
+                        goto completed;
+                } else  {
+                        retval = 0;
+                        goto completed;
+                }
+        } else if (page_code == PAGE_83) {
+                if (do_scsi_page83_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) {
+                        retval = 1;
+                        goto completed;
+                } else  {
+                        retval = 0;
+                        goto completed;
+                }
+        } else if (page_code == PAGE_83_PRE_SPC3) {
+                retval = do_scsi_page83_prespc3_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len);
+                if (retval) {
+                        /*
+                         * Fallback to servicing a SPC-2/3 compliant page 83
+                         * inquiry if the page 83 reply format does not
+                         * conform to pre-SPC3 expectations.
+                         */
+                        if (retval == 2) {
+                                if (do_scsi_page83_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) {
+                                        retval = 1;
+                                        goto completed;
+                                } else  {
+                                        retval = 0;
+                                        goto completed;
+                                }
+                        }
+                        else {
+                                retval = 1;
+                                goto completed;
+                        }
+                } else  {
+                        retval = 0;
+                        goto completed;
+                }
+        } else if (page_code != 0x00) {
+                info(udev, "%s: unsupported page code 0x%d\n", dev_scsi->kernel, page_code);
+                return 1;
+        }
+
+        /*
+         * Get page 0, the page of the pages. By default, try from best to
+         * worst of supported pages: 0x83 then 0x80.
+         */
+        if (do_scsi_page0_inquiry(udev, dev_scsi, fd, page0, SCSI_INQ_BUFF_LEN)) {
+                /*
+                 * Don't try anything else. Black list if a specific page
+                 * should be used for this vendor+model, or maybe have an
+                 * optional fall-back to page 0x80 or page 0x83.
+                 */
+                retval = 1;
+                goto completed;
+        }
+
+        dbg(udev, "%s: Checking page0\n", dev_scsi->kernel);
+
+        for (ind = 4; ind <= page0[3] + 3; ind++)
+                if (page0[ind] == PAGE_83)
+                        if (!do_scsi_page83_inquiry(udev, dev_scsi, fd,
+                                                    dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) {
+                                /*
+                                 * Success
+                                 */
+                                retval = 0;
+                                goto completed;
+                        }
+
+        for (ind = 4; ind <= page0[3] + 3; ind++)
+                if (page0[ind] == PAGE_80)
+                        if (!do_scsi_page80_inquiry(udev, dev_scsi, fd,
+                                                    dev_scsi->serial, dev_scsi->serial_short, len)) {
+                                /*
+                                 * Success
+                                 */
+                                retval = 0;
+                                goto completed;
+                        }
+        retval = 1;
+
+completed:
+        close(fd);
+        return retval;
+}
diff --git a/src/sd-daemon.c b/src/sd-daemon.c
new file mode 100644
index 0000000..763e079
--- /dev/null
+++ b/src/sd-daemon.c
@@ -0,0 +1,530 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef __BIONIC__
+#include <linux/fcntl.h>
+#else
+#include <sys/fcntl.h>
+#endif
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <limits.h>
+
+#if defined(__linux__)
+#include <mqueue.h>
+#endif
+
+#include "sd-daemon.h"
+
+#if (__GNUC__ >= 4)
+#ifdef SD_EXPORT_SYMBOLS
+/* Export symbols */
+#define _sd_export_ __attribute__ ((visibility("default")))
+#else
+/* Don't export the symbols */
+#define _sd_export_ __attribute__ ((visibility("hidden")))
+#endif
+#else
+#define _sd_export_
+#endif
+
+_sd_export_ int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        int r, fd;
+        const char *e;
+        char *p = NULL;
+        unsigned long l;
+
+        if (!(e = getenv("LISTEN_PID"))) {
+                r = 0;
+                goto finish;
+        }
+
+        errno = 0;
+        l = strtoul(e, &p, 10);
+
+        if (errno != 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!p || *p || l <= 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        /* Is this for us? */
+        if (getpid() != (pid_t) l) {
+                r = 0;
+                goto finish;
+        }
+
+        if (!(e = getenv("LISTEN_FDS"))) {
+                r = 0;
+                goto finish;
+        }
+
+        errno = 0;
+        l = strtoul(e, &p, 10);
+
+        if (errno != 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!p || *p) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+                int flags;
+
+                if ((flags = fcntl(fd, F_GETFD)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (flags & FD_CLOEXEC)
+                        continue;
+
+                if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        r = (int) l;
+
+finish:
+        if (unset_environment) {
+                unsetenv("LISTEN_PID");
+                unsetenv("LISTEN_FDS");
+        }
+
+        return r;
+#endif
+}
+
+_sd_export_ int sd_is_fifo(int fd, const char *path) {
+        struct stat st_fd;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        memset(&st_fd, 0, sizeof(st_fd));
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISFIFO(st_fd.st_mode))
+                return 0;
+
+        if (path) {
+                struct stat st_path;
+
+                memset(&st_path, 0, sizeof(st_path));
+                if (stat(path, &st_path) < 0) {
+
+                        if (errno == ENOENT || errno == ENOTDIR)
+                                return 0;
+
+                        return -errno;
+                }
+
+                return
+                        st_path.st_dev == st_fd.st_dev &&
+                        st_path.st_ino == st_fd.st_ino;
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_special(int fd, const char *path) {
+        struct stat st_fd;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode))
+                return 0;
+
+        if (path) {
+                struct stat st_path;
+
+                if (stat(path, &st_path) < 0) {
+
+                        if (errno == ENOENT || errno == ENOTDIR)
+                                return 0;
+
+                        return -errno;
+                }
+
+                if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode))
+                        return
+                                st_path.st_dev == st_fd.st_dev &&
+                                st_path.st_ino == st_fd.st_ino;
+                else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode))
+                        return st_path.st_rdev == st_fd.st_rdev;
+                else
+                        return 0;
+        }
+
+        return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+        struct stat st_fd;
+
+        if (fd < 0 || type < 0)
+                return -EINVAL;
+
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISSOCK(st_fd.st_mode))
+                return 0;
+
+        if (type != 0) {
+                int other_type = 0;
+                socklen_t l = sizeof(other_type);
+
+                if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+                        return -errno;
+
+                if (l != sizeof(other_type))
+                        return -EINVAL;
+
+                if (other_type != type)
+                        return 0;
+        }
+
+        if (listening >= 0) {
+                int accepting = 0;
+                socklen_t l = sizeof(accepting);
+
+                if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+                        return -errno;
+
+                if (l != sizeof(accepting))
+                        return -EINVAL;
+
+                if (!accepting != !listening)
+                        return 0;
+        }
+
+        return 1;
+}
+
+union sockaddr_union {
+        struct sockaddr sa;
+        struct sockaddr_in in4;
+        struct sockaddr_in6 in6;
+        struct sockaddr_un un;
+        struct sockaddr_storage storage;
+};
+
+_sd_export_ int sd_is_socket(int fd, int family, int type, int listening) {
+        int r;
+
+        if (family < 0)
+                return -EINVAL;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        if (family > 0) {
+                union sockaddr_union sockaddr;
+                socklen_t l;
+
+                memset(&sockaddr, 0, sizeof(sockaddr));
+                l = sizeof(sockaddr);
+
+                if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                        return -errno;
+
+                if (l < sizeof(sa_family_t))
+                        return -EINVAL;
+
+                return sockaddr.sa.sa_family == family;
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+        union sockaddr_union sockaddr;
+        socklen_t l;
+        int r;
+
+        if (family != 0 && family != AF_INET && family != AF_INET6)
+                return -EINVAL;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        l = sizeof(sockaddr);
+
+        if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                return -errno;
+
+        if (l < sizeof(sa_family_t))
+                return -EINVAL;
+
+        if (sockaddr.sa.sa_family != AF_INET &&
+            sockaddr.sa.sa_family != AF_INET6)
+                return 0;
+
+        if (family > 0)
+                if (sockaddr.sa.sa_family != family)
+                        return 0;
+
+        if (port > 0) {
+                if (sockaddr.sa.sa_family == AF_INET) {
+                        if (l < sizeof(struct sockaddr_in))
+                                return -EINVAL;
+
+                        return htons(port) == sockaddr.in4.sin_port;
+                } else {
+                        if (l < sizeof(struct sockaddr_in6))
+                                return -EINVAL;
+
+                        return htons(port) == sockaddr.in6.sin6_port;
+                }
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+        union sockaddr_union sockaddr;
+        socklen_t l;
+        int r;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        l = sizeof(sockaddr);
+
+        if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                return -errno;
+
+        if (l < sizeof(sa_family_t))
+                return -EINVAL;
+
+        if (sockaddr.sa.sa_family != AF_UNIX)
+                return 0;
+
+        if (path) {
+                if (length <= 0)
+                        length = strlen(path);
+
+                if (length <= 0)
+                        /* Unnamed socket */
+                        return l == offsetof(struct sockaddr_un, sun_path);
+
+                if (path[0])
+                        /* Normal path socket */
+                        return
+                                (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+                                memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+                else
+                        /* Abstract namespace socket */
+                        return
+                                (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+                                memcmp(path, sockaddr.un.sun_path, length) == 0;
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_mq(int fd, const char *path) {
+#if !defined(__linux__)
+        return 0;
+#else
+        struct mq_attr attr;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        if (mq_getattr(fd, &attr) < 0)
+                return -errno;
+
+        if (path) {
+                char fpath[PATH_MAX];
+                struct stat a, b;
+
+                if (path[0] != '/')
+                        return -EINVAL;
+
+                if (fstat(fd, &a) < 0)
+                        return -errno;
+
+                strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12);
+                fpath[sizeof(fpath)-1] = 0;
+
+                if (stat(fpath, &b) < 0)
+                        return -errno;
+
+                if (a.st_dev != b.st_dev ||
+                    a.st_ino != b.st_ino)
+                        return 0;
+        }
+
+        return 1;
+#endif
+}
+
+_sd_export_ int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+        return 0;
+#else
+        int fd = -1, r;
+        struct msghdr msghdr;
+        struct iovec iovec;
+        union sockaddr_union sockaddr;
+        const char *e;
+
+        if (!state) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if (!(e = getenv("NOTIFY_SOCKET")))
+                return 0;
+
+        /* Must be an abstract socket, or an absolute path */
+        if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        sockaddr.sa.sa_family = AF_UNIX;
+        strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+        if (sockaddr.un.sun_path[0] == '@')
+                sockaddr.un.sun_path[0] = 0;
+
+        memset(&iovec, 0, sizeof(iovec));
+        iovec.iov_base = (char*) state;
+        iovec.iov_len = strlen(state);
+
+        memset(&msghdr, 0, sizeof(msghdr));
+        msghdr.msg_name = &sockaddr;
+        msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+        if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+                msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+        msghdr.msg_iov = &iovec;
+        msghdr.msg_iovlen = 1;
+
+        if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 1;
+
+finish:
+        if (unset_environment)
+                unsetenv("NOTIFY_SOCKET");
+
+        if (fd >= 0)
+                close(fd);
+
+        return r;
+#endif
+}
+
+_sd_export_ int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        va_list ap;
+        char *p = NULL;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&p, format, ap);
+        va_end(ap);
+
+        if (r < 0 || !p)
+                return -ENOMEM;
+
+        r = sd_notify(unset_environment, p);
+        free(p);
+
+        return r;
+#endif
+}
+
+_sd_export_ int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+
+        struct stat a, b;
+
+        /* We simply test whether the systemd cgroup hierarchy is
+         * mounted */
+
+        if (lstat("/sys/fs/cgroup", &a) < 0)
+                return 0;
+
+        if (lstat("/sys/fs/cgroup/systemd", &b) < 0)
+                return 0;
+
+        return a.st_dev != b.st_dev;
+#endif
+}
diff --git a/src/sd-daemon.h b/src/sd-daemon.h
new file mode 100644
index 0000000..fe51159
--- /dev/null
+++ b/src/sd-daemon.h
@@ -0,0 +1,282 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+  Reference implementation of a few systemd related interfaces for
+  writing daemons. These interfaces are trivial to implement. To
+  simplify porting we provide this reference implementation.
+  Applications are welcome to reimplement the algorithms described
+  here if they do not want to include these two source files.
+
+  The following functionality is provided:
+
+  - Support for logging with log levels on stderr
+  - File descriptor passing for socket-based activation
+  - Daemon startup and status notification
+  - Detection of systemd boots
+
+  You may compile this with -DDISABLE_SYSTEMD to disable systemd
+  support. This makes all those calls NOPs that are directly related to
+  systemd (i.e. only sd_is_xxx() will stay useful).
+
+  Since this is drop-in code we don't want any of our symbols to be
+  exported in any case. Hence we declare hidden visibility for all of
+  them.
+
+  You may find an up-to-date version of these source files online:
+
+  http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h
+  http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c
+
+  This should compile on non-Linux systems, too, but with the
+  exception of the sd_is_xxx() calls all functions will become NOPs.
+
+  See sd-daemon(7) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+/*
+  Log levels for usage on stderr:
+
+          fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+  This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG   "<0>"  /* system is unusable */
+#define SD_ALERT   "<1>"  /* action must be taken immediately */
+#define SD_CRIT    "<2>"  /* critical conditions */
+#define SD_ERR     "<3>"  /* error conditions */
+#define SD_WARNING "<4>"  /* warning conditions */
+#define SD_NOTICE  "<5>"  /* normal but significant condition */
+#define SD_INFO    "<6>"  /* informational */
+#define SD_DEBUG   "<7>"  /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+  Returns how many file descriptors have been passed, or a negative
+  errno code on failure. Optionally, removes the $LISTEN_FDS and
+  $LISTEN_PID file descriptors from the environment (recommended, but
+  problematic in threaded environments). If r is the return value of
+  this function you'll find the file descriptors passed as fds
+  SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+  errno style error code on failure. This function call ensures that
+  the FD_CLOEXEC flag is set for the passed file descriptors, to make
+  sure they are not passed on to child processes. If FD_CLOEXEC shall
+  not be set, the caller needs to unset it after this call for all file
+  descriptors that are used.
+
+  See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a FIFO in the file system stored under the
+  specified path, 0 otherwise. If path is NULL a path name check will
+  not be done and the call only verifies if the file descriptor
+  refers to a FIFO. Returns a negative errno style error code on
+  failure.
+
+  See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a special character device on the file
+  system stored under the specified path, 0 otherwise.
+  If path is NULL a path name check will not be done and the call
+  only verifies if the file descriptor refers to a special character.
+  Returns a negative errno style error code on failure.
+
+  See sd_is_special(3) for more information.
+*/
+int sd_is_special(int fd, const char *path);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a socket of the specified family (AF_INET,
+  ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+  family is 0 a socket family check will not be done. If type is 0 a
+  socket type check will not be done and the call only verifies if
+  the file descriptor refers to a socket. If listening is > 0 it is
+  verified that the socket is in listening mode. (i.e. listen() has
+  been called) If listening is == 0 it is verified that the socket is
+  not in listening mode. If listening is < 0 no listening mode check
+  is done. Returns a negative errno style error code on failure.
+
+  See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an Internet socket, of the specified family
+  (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+  SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+  check is not done. If type is 0 a socket type check will not be
+  done. If port is 0 a socket port check will not be done. The
+  listening flag is used the same way as in sd_is_socket(). Returns a
+  negative errno style error code on failure.
+
+  See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an AF_UNIX socket of the specified type
+  (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+  a socket type check will not be done. If path is NULL a socket path
+  check will not be done. For normal AF_UNIX sockets set length to
+  0. For abstract namespace sockets set length to the length of the
+  socket name (including the initial 0 byte), and pass the full
+  socket path in path (including the initial 0 byte). The listening
+  flag is used the same way as in sd_is_socket(). Returns a negative
+  errno style error code on failure.
+
+  See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a POSIX Message Queue of the specified name,
+  0 otherwise. If path is NULL a message queue name check is not
+  done. Returns a negative errno style error code on failure.
+*/
+int sd_is_mq(int fd, const char *path);
+
+/*
+  Informs systemd about changed daemon state. This takes a number of
+  newline separated environment-style variable assignments in a
+  string. The following variables are known:
+
+     READY=1      Tells systemd that daemon startup is finished (only
+                  relevant for services of Type=notify). The passed
+                  argument is a boolean "1" or "0". Since there is
+                  little value in signaling non-readiness the only
+                  value daemons should send is "READY=1".
+
+     STATUS=...   Passes a single-line status string back to systemd
+                  that describes the daemon state. This is free-from
+                  and can be used for various purposes: general state
+                  feedback, fsck-like programs could pass completion
+                  percentages and failing programs could pass a human
+                  readable error message. Example: "STATUS=Completed
+                  66% of file system check..."
+
+     ERRNO=...    If a daemon fails, the errno-style error code,
+                  formatted as string. Example: "ERRNO=2" for ENOENT.
+
+     BUSERROR=... If a daemon fails, the D-Bus error-style error
+                  code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+     MAINPID=...  The main pid of a daemon, in case systemd did not
+                  fork off the process itself. Example: "MAINPID=4711"
+
+     WATCHDOG=1   Tells systemd to update the watchdog timestamp.
+                  Services using this feature should do this in
+                  regular intervals. A watchdog framework can use the
+                  timestamps to detect failed services.
+
+  Daemons can choose to send additional variables. However, it is
+  recommended to prefix variable names not listed above with X_.
+
+  Returns a negative errno-style error code on failure. Returns > 0
+  if systemd could be notified, 0 if it couldn't possibly because
+  systemd is not running.
+
+  Example: When a daemon finished starting up, it could issue this
+  call to notify systemd about it:
+
+     sd_notify(0, "READY=1");
+
+  See sd_notifyf() for more complete examples.
+
+  See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state);
+
+/*
+  Similar to sd_notify() but takes a format string.
+
+  Example 1: A daemon could send the following after initialization:
+
+     sd_notifyf(0, "READY=1\n"
+                   "STATUS=Processing requests...\n"
+                   "MAINPID=%lu",
+                   (unsigned long) getpid());
+
+  Example 2: A daemon could send the following shortly before
+  exiting, on failure:
+
+     sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+                   "ERRNO=%i",
+                   strerror(errno),
+                   errno);
+
+  See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3);
+
+/*
+  Returns > 0 if the system was booted with systemd. Returns < 0 on
+  error. Returns 0 if the system was not booted with systemd. Note
+  that all of the functions above handle non-systemd boots just
+  fine. You should NOT protect them with a call to this function. Also
+  note that this function checks whether the system, not the user
+  session is controlled by systemd. However the functions above work
+  for both user and system services.
+
+  See sd_booted(3) for more information.
+*/
+int sd_booted(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/test-libudev.c b/src/test-libudev.c
new file mode 100644
index 0000000..6161fb3
--- /dev/null
+++ b/src/test-libudev.c
@@ -0,0 +1,501 @@
+/*
+ * test-libudev
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+
+#include "libudev.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+static void log_fn(struct udev *udev,
+                   int priority, const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        printf("test-libudev: %s %s:%d ", fn, file, line);
+        vprintf(format, args);
+}
+
+static void print_device(struct udev_device *device)
+{
+        const char *str;
+        dev_t devnum;
+        int count;
+        struct udev_list_entry *list_entry;
+
+        printf("*** device: %p ***\n", device);
+        str = udev_device_get_action(device);
+        if (str != NULL)
+                printf("action:    '%s'\n", str);
+
+        str = udev_device_get_syspath(device);
+        printf("syspath:   '%s'\n", str);
+
+        str = udev_device_get_sysname(device);
+        printf("sysname:   '%s'\n", str);
+
+        str = udev_device_get_sysnum(device);
+        if (str != NULL)
+                printf("sysnum:    '%s'\n", str);
+
+        str = udev_device_get_devpath(device);
+        printf("devpath:   '%s'\n", str);
+
+        str = udev_device_get_subsystem(device);
+        if (str != NULL)
+                printf("subsystem: '%s'\n", str);
+
+        str = udev_device_get_devtype(device);
+        if (str != NULL)
+                printf("devtype:   '%s'\n", str);
+
+        str = udev_device_get_driver(device);
+        if (str != NULL)
+                printf("driver:    '%s'\n", str);
+
+        str = udev_device_get_devnode(device);
+        if (str != NULL)
+                printf("devname:   '%s'\n", str);
+
+        devnum = udev_device_get_devnum(device);
+        if (major(devnum) > 0)
+                printf("devnum:    %u:%u\n", major(devnum), minor(devnum));
+
+        count = 0;
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device)) {
+                printf("link:      '%s'\n", udev_list_entry_get_name(list_entry));
+                count++;
+        }
+        if (count > 0)
+                printf("found %i links\n", count);
+
+        count = 0;
+        udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) {
+                printf("property:  '%s=%s'\n",
+                       udev_list_entry_get_name(list_entry),
+                       udev_list_entry_get_value(list_entry));
+                count++;
+        }
+        if (count > 0)
+                printf("found %i properties\n", count);
+
+        str = udev_device_get_property_value(device, "MAJOR");
+        if (str != NULL)
+                printf("MAJOR: '%s'\n", str);
+
+        str = udev_device_get_sysattr_value(device, "dev");
+        if (str != NULL)
+                printf("attr{dev}: '%s'\n", str);
+
+        printf("\n");
+}
+
+static int test_device(struct udev *udev, const char *syspath)
+{
+        struct udev_device *device;
+
+        printf("looking at device: %s\n", syspath);
+        device = udev_device_new_from_syspath(udev, syspath);
+        if (device == NULL) {
+                printf("no device found\n");
+                return -1;
+        }
+        print_device(device);
+        udev_device_unref(device);
+        return 0;
+}
+
+static int test_device_parents(struct udev *udev, const char *syspath)
+{
+        struct udev_device *device;
+        struct udev_device *device_parent;
+
+        printf("looking at device: %s\n", syspath);
+        device = udev_device_new_from_syspath(udev, syspath);
+        if (device == NULL)
+                return -1;
+
+        printf("looking at parents\n");
+        device_parent = device;
+        do {
+                print_device(device_parent);
+                device_parent = udev_device_get_parent(device_parent);
+        } while (device_parent != NULL);
+
+        printf("looking at parents again\n");
+        device_parent = device;
+        do {
+                print_device(device_parent);
+                device_parent = udev_device_get_parent(device_parent);
+        } while (device_parent != NULL);
+        udev_device_unref(device);
+
+        return 0;
+}
+
+static int test_device_devnum(struct udev *udev)
+{
+        dev_t devnum = makedev(1, 3);
+        struct udev_device *device;
+
+        printf("looking up device: %u:%u\n", major(devnum), minor(devnum));
+        device = udev_device_new_from_devnum(udev, 'c', devnum);
+        if (device == NULL)
+                return -1;
+        print_device(device);
+        udev_device_unref(device);
+        return 0;
+}
+
+static int test_device_subsys_name(struct udev *udev)
+{
+        struct udev_device *device;
+
+        printf("looking up device: 'block':'sda'\n");
+        device = udev_device_new_from_subsystem_sysname(udev, "block", "sda");
+        if (device == NULL)
+                return -1;
+        print_device(device);
+        udev_device_unref(device);
+
+        printf("looking up device: 'subsystem':'pci'\n");
+        device = udev_device_new_from_subsystem_sysname(udev, "subsystem", "pci");
+        if (device == NULL)
+                return -1;
+        print_device(device);
+        udev_device_unref(device);
+
+        printf("looking up device: 'drivers':'scsi:sd'\n");
+        device = udev_device_new_from_subsystem_sysname(udev, "drivers", "scsi:sd");
+        if (device == NULL)
+                return -1;
+        print_device(device);
+        udev_device_unref(device);
+
+        printf("looking up device: 'module':'printk'\n");
+        device = udev_device_new_from_subsystem_sysname(udev, "module", "printk");
+        if (device == NULL)
+                return -1;
+        print_device(device);
+        udev_device_unref(device);
+        return 0;
+}
+
+static int test_enumerate_print_list(struct udev_enumerate *enumerate)
+{
+        struct udev_list_entry *list_entry;
+        int count = 0;
+
+        udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) {
+                struct udev_device *device;
+
+                device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate),
+                                                      udev_list_entry_get_name(list_entry));
+                if (device != NULL) {
+                        printf("device: '%s' (%s)\n",
+                               udev_device_get_syspath(device),
+                               udev_device_get_subsystem(device));
+                        udev_device_unref(device);
+                        count++;
+                }
+        }
+        printf("found %i devices\n\n", count);
+        return count;
+}
+
+static int test_monitor(struct udev *udev)
+{
+        struct udev_monitor *udev_monitor = NULL;
+        int fd_ep;
+        int fd_udev = -1;
+        struct epoll_event ep_udev, ep_stdin;
+
+        fd_ep = epoll_create1(EPOLL_CLOEXEC);
+        if (fd_ep < 0) {
+                printf("error creating epoll fd: %m\n");
+                goto out;
+        }
+
+        udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
+        if (udev_monitor == NULL) {
+                printf("no socket\n");
+                goto out;
+        }
+        fd_udev = udev_monitor_get_fd(udev_monitor);
+
+        if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "block", NULL) < 0 ||
+            udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "tty", NULL) < 0 ||
+            udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", "usb_device") < 0) {
+                printf("filter failed\n");
+                goto out;
+        }
+
+        if (udev_monitor_enable_receiving(udev_monitor) < 0) {
+                printf("bind failed\n");
+                goto out;
+        }
+
+        memset(&ep_udev, 0, sizeof(struct epoll_event));
+        ep_udev.events = EPOLLIN;
+        ep_udev.data.fd = fd_udev;
+        if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) {
+                printf("fail to add fd to epoll: %m\n");
+                goto out;
+        }
+
+        memset(&ep_stdin, 0, sizeof(struct epoll_event));
+        ep_stdin.events = EPOLLIN;
+        ep_stdin.data.fd = STDIN_FILENO;
+        if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, STDIN_FILENO, &ep_stdin) < 0) {
+                printf("fail to add fd to epoll: %m\n");
+                goto out;
+        }
+
+        for (;;) {
+                int fdcount;
+                struct epoll_event ev[4];
+                struct udev_device *device;
+                int i;
+
+                printf("waiting for events from udev, press ENTER to exit\n");
+                fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), -1);
+                printf("epoll fd count: %i\n", fdcount);
+
+                for (i = 0; i < fdcount; i++) {
+                        if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) {
+                                device = udev_monitor_receive_device(udev_monitor);
+                                if (device == NULL) {
+                                        printf("no device from socket\n");
+                                        continue;
+                                }
+                                print_device(device);
+                                udev_device_unref(device);
+                        } else if (ev[i].data.fd == STDIN_FILENO && ev[i].events & EPOLLIN) {
+                                printf("exiting loop\n");
+                                goto out;
+                        }
+                }
+        }
+out:
+        if (fd_ep >= 0)
+                close(fd_ep);
+        udev_monitor_unref(udev_monitor);
+        return 0;
+}
+
+static int test_queue(struct udev *udev)
+{
+        struct udev_queue *udev_queue;
+        unsigned long long int seqnum;
+        struct udev_list_entry *list_entry;
+
+        udev_queue = udev_queue_new(udev);
+        if (udev_queue == NULL)
+                return -1;
+        seqnum = udev_queue_get_kernel_seqnum(udev_queue);
+        printf("seqnum kernel: %llu\n", seqnum);
+        seqnum = udev_queue_get_udev_seqnum(udev_queue);
+        printf("seqnum udev  : %llu\n", seqnum);
+
+        if (udev_queue_get_queue_is_empty(udev_queue))
+                printf("queue is empty\n");
+        printf("get queue list\n");
+        udev_list_entry_foreach(list_entry, udev_queue_get_queued_list_entry(udev_queue))
+                printf("queued: '%s' [%s]\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry));
+        printf("\n");
+        printf("get queue list again\n");
+        udev_list_entry_foreach(list_entry, udev_queue_get_queued_list_entry(udev_queue))
+                printf("queued: '%s' [%s]\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry));
+        printf("\n");
+
+        list_entry = udev_queue_get_queued_list_entry(udev_queue);
+        if (list_entry != NULL) {
+                printf("event [%llu] is queued\n", seqnum);
+                seqnum = strtoull(udev_list_entry_get_value(list_entry), NULL, 10);
+                if (udev_queue_get_seqnum_is_finished(udev_queue, seqnum))
+                        printf("event [%llu] is not finished\n", seqnum);
+                else
+                        printf("event [%llu] is finished\n", seqnum);
+        }
+        printf("\n");
+        udev_queue_unref(udev_queue);
+        return 0;
+}
+
+static int test_enumerate(struct udev *udev, const char *subsystem)
+{
+        struct udev_enumerate *udev_enumerate;
+
+        printf("enumerate '%s'\n", subsystem == NULL ? "<all>" : subsystem);
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_add_match_subsystem(udev_enumerate, subsystem);
+        udev_enumerate_scan_devices(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+
+        printf("enumerate 'net' + duplicated scan + null + zero\n");
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_add_match_subsystem(udev_enumerate, "net");
+        udev_enumerate_scan_devices(udev_enumerate);
+        udev_enumerate_scan_devices(udev_enumerate);
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero");
+        udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero");
+        udev_enumerate_scan_devices(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+
+        printf("enumerate 'block'\n");
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_add_match_subsystem(udev_enumerate,"block");
+        udev_enumerate_add_match_is_initialized(udev_enumerate);
+        udev_enumerate_scan_devices(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+
+        printf("enumerate 'not block'\n");
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_add_nomatch_subsystem(udev_enumerate, "block");
+        udev_enumerate_scan_devices(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+
+        printf("enumerate 'pci, mem, vc'\n");
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_add_match_subsystem(udev_enumerate, "pci");
+        udev_enumerate_add_match_subsystem(udev_enumerate, "mem");
+        udev_enumerate_add_match_subsystem(udev_enumerate, "vc");
+        udev_enumerate_scan_devices(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+
+        printf("enumerate 'subsystem'\n");
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_scan_subsystems(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+
+        printf("enumerate 'property IF_FS_*=filesystem'\n");
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_add_match_property(udev_enumerate, "ID_FS*", "filesystem");
+        udev_enumerate_scan_devices(udev_enumerate);
+        test_enumerate_print_list(udev_enumerate);
+        udev_enumerate_unref(udev_enumerate);
+        return 0;
+}
+
+int main(int argc, char *argv[])
+{
+        struct udev *udev = NULL;
+        static const struct option options[] = {
+                { "syspath", required_argument, NULL, 'p' },
+                { "subsystem", required_argument, NULL, 's' },
+                { "debug", no_argument, NULL, 'd' },
+                { "help", no_argument, NULL, 'h' },
+                { "version", no_argument, NULL, 'V' },
+                {}
+        };
+        const char *syspath = "/devices/virtual/mem/null";
+        const char *subsystem = NULL;
+        char path[1024];
+        const char *str;
+
+        udev = udev_new();
+        printf("context: %p\n", udev);
+        if (udev == NULL) {
+                printf("no context\n");
+                return 1;
+        }
+        udev_set_log_fn(udev, log_fn);
+        printf("set log: %p\n", log_fn);
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "+p:s:dhV", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'p':
+                        syspath = optarg;
+                        break;
+                case 's':
+                        subsystem = optarg;
+                        break;
+                case 'd':
+                        if (udev_get_log_priority(udev) < LOG_INFO)
+                                udev_set_log_priority(udev, LOG_INFO);
+                        break;
+                case 'h':
+                        printf("--debug --syspath= --subsystem= --help\n");
+                        goto out;
+                case 'V':
+                        printf("%s\n", VERSION);
+                        goto out;
+                default:
+                        goto out;
+                }
+        }
+
+        str = udev_get_sys_path(udev);
+        printf("sys_path: '%s'\n", str);
+        str = udev_get_dev_path(udev);
+        printf("dev_path: '%s'\n", str);
+
+        /* add sys path if needed */
+        if (strncmp(syspath, udev_get_sys_path(udev), strlen(udev_get_sys_path(udev))) != 0) {
+                snprintf(path, sizeof(path), "%s%s", udev_get_sys_path(udev), syspath);
+                syspath = path;
+        }
+
+        test_device(udev, syspath);
+        test_device_devnum(udev);
+        test_device_subsys_name(udev);
+        test_device_parents(udev, syspath);
+
+        test_enumerate(udev, subsystem);
+
+        test_queue(udev);
+
+        test_monitor(udev);
+out:
+        udev_unref(udev);
+        return 0;
+}
diff --git a/src/test-udev.c b/src/test-udev.c
new file mode 100644
index 0000000..c9712e9
--- /dev/null
+++ b/src/test-udev.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <grp.h>
+#include <sys/signalfd.h>
+
+#include "udev.h"
+
+void udev_main_log(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args) {}
+
+int main(int argc, char *argv[])
+{
+        struct udev *udev;
+        struct udev_event *event = NULL;
+        struct udev_device *dev = NULL;
+        struct udev_rules *rules = NULL;
+        char syspath[UTIL_PATH_SIZE];
+        const char *devpath;
+        const char *action;
+        sigset_t mask, sigmask_orig;
+        int err = -EINVAL;
+
+        udev = udev_new();
+        if (udev == NULL)
+                exit(1);
+        info(udev, "version %s\n", VERSION);
+        udev_selinux_init(udev);
+
+        sigprocmask(SIG_SETMASK, NULL, &sigmask_orig);
+
+        action = argv[1];
+        if (action == NULL) {
+                err(udev, "action missing\n");
+                goto out;
+        }
+
+        devpath = argv[2];
+        if (devpath == NULL) {
+                err(udev, "devpath missing\n");
+                goto out;
+        }
+
+        rules = udev_rules_new(udev, 1);
+
+        util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), devpath, NULL);
+        dev = udev_device_new_from_syspath(udev, syspath);
+        if (dev == NULL) {
+                info(udev, "unknown device '%s'\n", devpath);
+                goto out;
+        }
+
+        udev_device_set_action(dev, action);
+        event = udev_event_new(dev);
+
+        sigfillset(&mask);
+        sigprocmask(SIG_SETMASK, &mask, &sigmask_orig);
+        event->fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+        if (event->fd_signal < 0) {
+                fprintf(stderr, "error creating signalfd\n");
+                goto out;
+        }
+
+        /* do what devtmpfs usually provides us */
+        if (udev_device_get_devnode(dev) != NULL) {
+                mode_t mode;
+
+                if (strcmp(udev_device_get_subsystem(dev), "block") == 0)
+                        mode |= S_IFBLK;
+                else
+                        mode |= S_IFCHR;
+
+                if (strcmp(action, "remove") != 0) {
+                        util_create_path(udev, udev_device_get_devnode(dev));
+                        mknod(udev_device_get_devnode(dev), mode, udev_device_get_devnum(dev));
+                } else {
+                        unlink(udev_device_get_devnode(dev));
+                        util_delete_path(udev, udev_device_get_devnode(dev));
+                }
+        }
+
+        err = udev_event_execute_rules(event, rules, &sigmask_orig);
+        if (err == 0)
+                udev_event_execute_run(event, NULL);
+out:
+        if (event != NULL && event->fd_signal >= 0)
+                close(event->fd_signal);
+        udev_event_unref(event);
+        udev_device_unref(dev);
+        udev_rules_unref(rules);
+        udev_selinux_exit(udev);
+        udev_unref(udev);
+        if (err != 0)
+                return 1;
+        return 0;
+}
diff --git a/src/udev-builtin-blkid.c b/src/udev-builtin-blkid.c
new file mode 100644
index 0000000..e57f03e
--- /dev/null
+++ b/src/udev-builtin-blkid.c
@@ -0,0 +1,207 @@
+/*
+ * probe disks for filesystems and partitions
+ *
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <blkid/blkid.h>
+
+#include "udev.h"
+
+static void print_property(struct udev_device *dev, bool test, const char *name, const char *value)
+{
+        char s[265];
+
+        s[0] = '\0';
+
+        if (!strcmp(name, "TYPE")) {
+                udev_builtin_add_property(dev, test, "ID_FS_TYPE", value);
+
+        } else if (!strcmp(name, "USAGE")) {
+                udev_builtin_add_property(dev, test, "ID_FS_USAGE", value);
+
+        } else if (!strcmp(name, "VERSION")) {
+                udev_builtin_add_property(dev, test, "ID_FS_VERSION", value);
+
+        } else if (!strcmp(name, "UUID")) {
+                blkid_safe_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_FS_UUID", s);
+                blkid_encode_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_FS_UUID_ENC", s);
+
+        } else if (!strcmp(name, "UUID_SUB")) {
+                blkid_safe_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB", s);
+                blkid_encode_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB_ENC", s);
+
+        } else if (!strcmp(name, "LABEL")) {
+                blkid_safe_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_FS_LABEL", s);
+                blkid_encode_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_FS_LABEL_ENC", s);
+
+        } else if (!strcmp(name, "PTTYPE")) {
+                udev_builtin_add_property(dev, test, "ID_PART_TABLE_TYPE", value);
+
+        } else if (!strcmp(name, "PART_ENTRY_NAME")) {
+                blkid_encode_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_PART_ENTRY_NAME", s);
+
+        } else if (!strcmp(name, "PART_ENTRY_TYPE")) {
+                blkid_encode_string(value, s, sizeof(s));
+                udev_builtin_add_property(dev, test, "ID_PART_ENTRY_TYPE", s);
+
+        } else if (!strncmp(name, "PART_ENTRY_", 11)) {
+                util_strscpyl(s, sizeof(s), "ID_", name, NULL);
+                udev_builtin_add_property(dev, test, s, value);
+        }
+}
+
+static int probe_superblocks(blkid_probe pr)
+{
+        struct stat st;
+        int rc;
+
+        if (fstat(blkid_probe_get_fd(pr), &st))
+                return -1;
+
+        blkid_probe_enable_partitions(pr, 1);
+
+        if (!S_ISCHR(st.st_mode) && blkid_probe_get_size(pr) <= 1024 * 1440 &&
+            blkid_probe_is_wholedisk(pr)) {
+                /*
+                 * check if the small disk is partitioned, if yes then
+                 * don't probe for filesystems.
+                 */
+                blkid_probe_enable_superblocks(pr, 0);
+
+                rc = blkid_do_fullprobe(pr);
+                if (rc < 0)
+                        return rc;        /* -1 = error, 1 = nothing, 0 = succes */
+
+                if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0)
+                        return 0;        /* partition table detected */
+        }
+
+        blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
+        blkid_probe_enable_superblocks(pr, 1);
+
+        return blkid_do_safeprobe(pr);
+}
+
+static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        int64_t offset = 0;
+        bool noraid = false;
+        int fd = -1;
+        blkid_probe pr;
+        const char *data;
+        const char *name;
+        int nvals;
+        int i;
+        size_t len;
+        int err = 0;
+
+        static const struct option options[] = {
+                { "offset", optional_argument, NULL, 'o' },
+                { "noraid", no_argument, NULL, 'R' },
+                {}
+        };
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "oR", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'o':
+                        offset = strtoull(optarg, NULL, 0);
+                        break;
+                case 'R':
+                        noraid = true;
+                        break;
+                }
+        }
+
+        pr = blkid_new_probe();
+        if (!pr) {
+                err = -ENOMEM;
+                return EXIT_FAILURE;
+        }
+
+        blkid_probe_set_superblocks_flags(pr,
+                BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+                BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
+                BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION);
+
+        if (noraid)
+                blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID);
+
+        fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC);
+        if (fd < 0) {
+                fprintf(stderr, "error: %s: %m\n", udev_device_get_devnode(dev));
+                goto out;
+        }
+
+        err = blkid_probe_set_device(pr, fd, offset, 0);
+        if (err < 0)
+                goto out;
+
+        info(udev, "probe %s %sraid offset=%llu\n",
+             udev_device_get_devnode(dev),
+             noraid ? "no" : "", (unsigned long long) offset);
+
+        err = probe_superblocks(pr);
+        if (err < 0)
+                goto out;
+
+        nvals = blkid_probe_numof_values(pr);
+        for (i = 0; i < nvals; i++) {
+                if (blkid_probe_get_value(pr, i, &name, &data, &len))
+                        continue;
+                len = strnlen((char *) data, len);
+                print_property(dev, test, name, (char *) data);
+        }
+
+        blkid_free_probe(pr);
+out:
+        if (fd > 0)
+                close(fd);
+        if (err < 0)
+                return EXIT_FAILURE;
+        return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_blkid = {
+        .name = "blkid",
+        .cmd = builtin_blkid,
+        .help = "filesystem and partition probing",
+        .run_once = true,
+};
diff --git a/src/udev-builtin-firmware.c b/src/udev-builtin-firmware.c
new file mode 100644
index 0000000..d212c64
--- /dev/null
+++ b/src/udev-builtin-firmware.c
@@ -0,0 +1,168 @@
+/*
+ * firmware - Kernel firmware loader
+ *
+ * Copyright (C) 2009 Piter Punk <piterpunk@slackware.com>
+ * Copyright (C) 2009-2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details:*
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <sys/utsname.h>
+#include <sys/stat.h>
+
+#include "udev.h"
+
+static bool set_loading(struct udev *udev, char *loadpath, const char *state)
+{
+        FILE *ldfile;
+
+        ldfile = fopen(loadpath, "we");
+        if (ldfile == NULL) {
+                err(udev, "error: can not open '%s'\n", loadpath);
+                return false;
+        };
+        fprintf(ldfile, "%s\n", state);
+        fclose(ldfile);
+        return true;
+}
+
+static bool copy_firmware(struct udev *udev, const char *source, const char *target, size_t size)
+{
+        char *buf;
+        FILE *fsource = NULL, *ftarget = NULL;
+        bool ret = false;
+
+        buf = malloc(size);
+        if (buf == NULL) {
+                err(udev,"No memory available to load firmware file");
+                return false;
+        }
+
+        info(udev, "writing '%s' (%zi) to '%s'\n", source, size, target);
+
+        fsource = fopen(source, "re");
+        if (fsource == NULL)
+                goto exit;
+        ftarget = fopen(target, "we");
+        if (ftarget == NULL)
+                goto exit;
+        if (fread(buf, size, 1, fsource) != 1)
+                goto exit;
+        if (fwrite(buf, size, 1, ftarget) == 1)
+                ret = true;
+exit:
+        if (ftarget != NULL)
+                fclose(ftarget);
+        if (fsource != NULL)
+                fclose(fsource);
+        free(buf);
+        return ret;
+}
+
+static int builtin_firmware(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        static const char *searchpath[] = { FIRMWARE_PATH };
+        char fwencpath[UTIL_PATH_SIZE];
+        char misspath[UTIL_PATH_SIZE];
+        char loadpath[UTIL_PATH_SIZE];
+        char datapath[UTIL_PATH_SIZE];
+        char fwpath[UTIL_PATH_SIZE];
+        const char *firmware;
+        FILE *fwfile;
+        struct utsname kernel;
+        struct stat statbuf;
+        unsigned int i;
+        int rc = EXIT_SUCCESS;
+
+        firmware = udev_device_get_property_value(dev, "FIRMWARE");
+        if (firmware == NULL) {
+                err(udev, "firmware parameter missing\n\n");
+                rc = EXIT_FAILURE;
+                goto exit;
+        }
+
+        /* lookup firmware file */
+        uname(&kernel);
+        for (i = 0; i < ARRAY_SIZE(searchpath); i++) {
+                util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], kernel.release, "/", firmware, NULL);
+                dbg(udev, "trying %s\n", fwpath);
+                fwfile = fopen(fwpath, "re");
+                if (fwfile != NULL)
+                        break;
+
+                util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], firmware, NULL);
+                dbg(udev, "trying %s\n", fwpath);
+                fwfile = fopen(fwpath, "re");
+                if (fwfile != NULL)
+                        break;
+        }
+
+        util_path_encode(firmware, fwencpath, sizeof(fwencpath));
+        util_strscpyl(misspath, sizeof(misspath), udev_get_run_path(udev), "/firmware-missing/", fwencpath, NULL);
+        util_strscpyl(loadpath, sizeof(loadpath), udev_device_get_syspath(dev), "/loading", NULL);
+
+        if (fwfile == NULL) {
+                int err;
+
+                /* This link indicates the missing firmware file and the associated device */
+                info(udev, "did not find firmware file '%s'\n", firmware);
+                do {
+                        err = util_create_path(udev, misspath);
+                        if (err != 0 && err != -ENOENT)
+                                break;
+                        err = symlink(udev_device_get_devpath(dev), misspath);
+                        if (err != 0)
+                                err = -errno;
+                } while (err == -ENOENT);
+                rc = EXIT_FAILURE;
+                set_loading(udev, loadpath, "-1");
+                goto exit;
+        }
+
+        if (stat(fwpath, &statbuf) < 0 || statbuf.st_size == 0) {
+                rc = EXIT_FAILURE;
+                goto exit;
+        }
+        if (unlink(misspath) == 0)
+                util_delete_path(udev, misspath);
+
+        if (!set_loading(udev, loadpath, "1"))
+                goto exit;
+
+        util_strscpyl(datapath, sizeof(datapath), udev_device_get_syspath(dev), "/data", NULL);
+        if (!copy_firmware(udev, fwpath, datapath, statbuf.st_size)) {
+                err(udev, "error sending firmware '%s' to device\n", firmware);
+                set_loading(udev, loadpath, "-1");
+                rc = EXIT_FAILURE;
+                goto exit;
+        };
+
+        set_loading(udev, loadpath, "0");
+exit:
+        if (fwfile)
+                fclose(fwfile);
+        return rc;
+}
+
+const struct udev_builtin udev_builtin_firmware = {
+        .name = "firmware",
+        .cmd = builtin_firmware,
+        .help = "kernel firmware loader",
+        .run_once = true,
+};
diff --git a/src/udev-builtin-hwdb.c b/src/udev-builtin-hwdb.c
new file mode 100644
index 0000000..aa996f3
--- /dev/null
+++ b/src/udev-builtin-hwdb.c
@@ -0,0 +1,247 @@
+/*
+ * usb-db, pci-db - lookup vendor/product database
+ *
+ * Copyright (C) 2009 Lennart Poettering <lennart@poettering.net>
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "udev.h"
+
+static int get_id_attr(
+        struct udev_device *parent,
+        const char *name,
+        uint16_t *value) {
+
+        const char *t;
+        unsigned u;
+
+        if (!(t = udev_device_get_sysattr_value(parent, name))) {
+                fprintf(stderr, "%s lacks %s.\n", udev_device_get_syspath(parent), name);
+                return -1;
+        }
+
+        if (!strncmp(t, "0x", 2))
+                t += 2;
+
+        if (sscanf(t, "%04x", &u) != 1 || u > 0xFFFFU) {
+                fprintf(stderr, "Failed to parse %s on %s.\n", name, udev_device_get_syspath(parent));
+                return -1;
+        }
+
+        *value = (uint16_t) u;
+        return 0;
+}
+
+static int get_vid_pid(
+        struct udev_device *parent,
+        const char *vendor_attr,
+        const char *product_attr,
+        uint16_t *vid,
+        uint16_t *pid) {
+
+        if (get_id_attr(parent, vendor_attr, vid) < 0)
+                return -1;
+        else if (*vid <= 0) {
+                fprintf(stderr, "Invalid vendor id.\n");
+                return -1;
+        }
+
+        if (get_id_attr(parent, product_attr, pid) < 0)
+                return -1;
+
+        return 0;
+}
+
+static void rstrip(char *n) {
+        size_t i;
+
+        for (i = strlen(n); i > 0 && isspace(n[i-1]); i--)
+                n[i-1] = 0;
+}
+
+#define HEXCHARS "0123456789abcdefABCDEF"
+#define WHITESPACE " \t\n\r"
+static int lookup_vid_pid(const char *database,
+                          uint16_t vid, uint16_t pid,
+                          char **vendor, char **product)
+{
+
+        FILE *f;
+        int ret = -1;
+        int found_vendor = 0;
+        char *line = NULL;
+
+        *vendor = *product = NULL;
+
+        if (!(f = fopen(database, "rme"))) {
+                fprintf(stderr, "Failed to open database file '%s': %s\n", database, strerror(errno));
+                return -1;
+        }
+
+        for (;;) {
+                size_t n;
+
+                if (getline(&line, &n, f) < 0)
+                        break;
+
+                rstrip(line);
+
+                if (line[0] == '#' || line[0] == 0)
+                        continue;
+
+                if (strspn(line, HEXCHARS) == 4) {
+                        unsigned u;
+
+                        if (found_vendor)
+                                break;
+
+                        if (sscanf(line, "%04x", &u) == 1 && u == vid) {
+                                char *t;
+
+                                t = line+4;
+                                t += strspn(t, WHITESPACE);
+
+                                if (!(*vendor = strdup(t))) {
+                                        fprintf(stderr, "Out of memory.\n");
+                                        goto finish;
+                                }
+
+                                found_vendor = 1;
+                        }
+
+                        continue;
+                }
+
+                if (found_vendor && line[0] == '\t' && strspn(line+1, HEXCHARS) == 4) {
+                        unsigned u;
+
+                        if (sscanf(line+1, "%04x", &u) == 1 && u == pid) {
+                                char *t;
+
+                                t = line+5;
+                                t += strspn(t, WHITESPACE);
+
+                                if (!(*product = strdup(t))) {
+                                        fprintf(stderr, "Out of memory.\n");
+                                        goto finish;
+                                }
+
+                                break;
+                        }
+                }
+        }
+
+        ret = 0;
+
+finish:
+        free(line);
+        fclose(f);
+
+        if (ret < 0) {
+                free(*product);
+                free(*vendor);
+
+                *product = *vendor = NULL;
+        }
+
+        return ret;
+}
+
+static struct udev_device *find_device(struct udev_device *dev, const char *subsys, const char *devtype)
+{
+        const char *str;
+
+        str = udev_device_get_subsystem(dev);
+        if (str == NULL)
+                goto try_parent;
+        if (strcmp(str, subsys) != 0)
+                goto try_parent;
+
+        if (devtype != NULL) {
+                str = udev_device_get_devtype(dev);
+                if (str == NULL)
+                        goto try_parent;
+                if (strcmp(str, devtype) != 0)
+                        goto try_parent;
+        }
+        return dev;
+try_parent:
+        return udev_device_get_parent_with_subsystem_devtype(dev, subsys, devtype);
+}
+
+
+static int builtin_db(struct udev_device *dev, bool test,
+                      const char *database,
+                      const char *vendor_attr, const char *product_attr,
+                      const char *subsys, const char *devtype)
+{
+        struct udev_device *parent;
+        uint16_t vid = 0, pid = 0;
+        char *vendor = NULL, *product = NULL;
+
+        parent = find_device(dev, subsys, devtype);
+        if (!parent) {
+                fprintf(stderr, "Failed to find device.\n");
+                goto finish;
+        }
+
+        if (get_vid_pid(parent, vendor_attr, product_attr, &vid, &pid) < 0)
+                goto finish;
+
+        if (lookup_vid_pid(database, vid, pid, &vendor, &product) < 0)
+                goto finish;
+
+        if (vendor)
+                udev_builtin_add_property(dev, test, "ID_VENDOR_FROM_DATABASE", vendor);
+        if (product)
+                udev_builtin_add_property(dev, test, "ID_MODEL_FROM_DATABASE", product);
+
+finish:
+        free(vendor);
+        free(product);
+        return 0;
+}
+
+static int builtin_usb_db(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        return builtin_db(dev, test, USB_DATABASE, "idVendor", "idProduct", "usb", "usb_device");
+}
+
+static int builtin_pci_db(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        return builtin_db(dev, test, PCI_DATABASE, "vendor", "device", "pci", NULL);
+}
+
+const struct udev_builtin udev_builtin_usb_db = {
+        .name = "usb-db",
+        .cmd = builtin_usb_db,
+        .help = "USB vendor/product database",
+        .run_once = true,
+};
+
+const struct udev_builtin udev_builtin_pci_db = {
+        .name = "pci-db",
+        .cmd = builtin_pci_db,
+        .help = "PCI vendor/product database",
+        .run_once = true,
+};
diff --git a/src/udev-builtin-input_id.c b/src/udev-builtin-input_id.c
new file mode 100644
index 0000000..a062ef7
--- /dev/null
+++ b/src/udev-builtin-input_id.c
@@ -0,0 +1,218 @@
+/*
+ * compose persistent device path
+ *
+ * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com>
+ * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk>
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/limits.h>
+#include <linux/input.h>
+
+#include "udev.h"
+
+/* we must use this kernel-compatible implementation */
+#define BITS_PER_LONG (sizeof(unsigned long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x)  ((x)%BITS_PER_LONG)
+#define BIT(x)  (1UL<<OFF(x))
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array)    ((array[LONG(bit)] >> OFF(bit)) & 1)
+
+/*
+ * Read a capability attribute and return bitmask.
+ * @param dev udev_device
+ * @param attr sysfs attribute name (e. g. "capabilities/key")
+ * @param bitmask: Output array which has a sizeof of bitmask_size
+ */
+static void get_cap_mask(struct udev_device *dev,
+                         struct udev_device *pdev, const char* attr,
+                         unsigned long *bitmask, size_t bitmask_size,
+                         bool test)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        char text[4096];
+        unsigned i;
+        char* word;
+        unsigned long val;
+
+        snprintf(text, sizeof(text), "%s", udev_device_get_sysattr_value(pdev, attr));
+        info(udev, "%s raw kernel attribute: %s\n", attr, text);
+
+        memset (bitmask, 0, bitmask_size);
+        i = 0;
+        while ((word = strrchr(text, ' ')) != NULL) {
+                val = strtoul (word+1, NULL, 16);
+                if (i < bitmask_size/sizeof(unsigned long))
+                        bitmask[i] = val;
+                else
+                        info(udev, "ignoring %s block %lX which is larger than maximum size\n", attr, val);
+                *word = '\0';
+                ++i;
+        }
+        val = strtoul (text, NULL, 16);
+        if (i < bitmask_size / sizeof(unsigned long))
+                bitmask[i] = val;
+        else
+                info(udev, "ignoring %s block %lX which is larger than maximum size\n", attr, val);
+
+        if (test) {
+                /* printf pattern with the right unsigned long number of hex chars */
+                snprintf(text, sizeof(text), "  bit %%4u: %%0%zilX\n", 2 * sizeof(unsigned long));
+                info(udev, "%s decoded bit map:\n", attr);
+                val = bitmask_size / sizeof (unsigned long);
+                /* skip over leading zeros */
+                while (bitmask[val-1] == 0 && val > 0)
+                        --val;
+                for (i = 0; i < val; ++i)
+                        info(udev, text, i * BITS_PER_LONG, bitmask[i]);
+        }
+}
+
+/* pointer devices */
+static void test_pointers (struct udev_device *dev,
+                           const unsigned long* bitmask_ev,
+                           const unsigned long* bitmask_abs,
+                           const unsigned long* bitmask_key,
+                           const unsigned long* bitmask_rel,
+                           bool test)
+{
+        int is_mouse = 0;
+        int is_touchpad = 0;
+
+        if (!test_bit (EV_KEY, bitmask_ev)) {
+                if (test_bit (EV_ABS, bitmask_ev) &&
+                    test_bit (ABS_X, bitmask_abs) &&
+                    test_bit (ABS_Y, bitmask_abs) &&
+                    test_bit (ABS_Z, bitmask_abs))
+                        udev_builtin_add_property(dev, test, "ID_INPUT_ACCELEROMETER", "1");
+                return;
+        }
+
+        if (test_bit (EV_ABS, bitmask_ev) &&
+            test_bit (ABS_X, bitmask_abs) && test_bit (ABS_Y, bitmask_abs)) {
+                if (test_bit (BTN_STYLUS, bitmask_key) || test_bit (BTN_TOOL_PEN, bitmask_key))
+                        udev_builtin_add_property(dev, test, "ID_INPUT_TABLET", "1");
+                else if (test_bit (BTN_TOOL_FINGER, bitmask_key) && !test_bit (BTN_TOOL_PEN, bitmask_key))
+                        is_touchpad = 1;
+                else if (test_bit (BTN_TRIGGER, bitmask_key) ||
+                         test_bit (BTN_A, bitmask_key) ||
+                         test_bit (BTN_1, bitmask_key))
+                        udev_builtin_add_property(dev, test, "ID_INPUT_JOYSTICK", "1");
+                else if (test_bit (BTN_MOUSE, bitmask_key))
+                        /* This path is taken by VMware's USB mouse, which has
+                         * absolute axes, but no touch/pressure button. */
+                        is_mouse = 1;
+                else if (test_bit (BTN_TOUCH, bitmask_key))
+                        udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHSCREEN", "1");
+        }
+
+        if (test_bit (EV_REL, bitmask_ev) &&
+            test_bit (REL_X, bitmask_rel) && test_bit (REL_Y, bitmask_rel) &&
+            test_bit (BTN_MOUSE, bitmask_key))
+                is_mouse = 1;
+
+        if (is_mouse)
+                udev_builtin_add_property(dev, test, "ID_INPUT_MOUSE", "1");
+        if (is_touchpad)
+                udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHPAD", "1");
+}
+
+/* key like devices */
+static void test_key (struct udev_device *dev,
+                      const unsigned long* bitmask_ev,
+                      const unsigned long* bitmask_key,
+                      bool test)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        unsigned i;
+        unsigned long found;
+        unsigned long mask;
+
+        /* do we have any KEY_* capability? */
+        if (!test_bit (EV_KEY, bitmask_ev)) {
+                info(udev, "test_key: no EV_KEY capability\n");
+                return;
+        }
+
+        /* only consider KEY_* here, not BTN_* */
+        found = 0;
+        for (i = 0; i < BTN_MISC/BITS_PER_LONG; ++i) {
+                found |= bitmask_key[i];
+                info(udev, "test_key: checking bit block %lu for any keys; found=%i\n", i*BITS_PER_LONG, found > 0);
+        }
+        /* If there are no keys in the lower block, check the higher block */
+        if (!found) {
+                for (i = KEY_OK; i < BTN_TRIGGER_HAPPY; ++i) {
+                        if (test_bit (i, bitmask_key)) {
+                                info(udev, "test_key: Found key %x in high block\n", i);
+                                found = 1;
+                                break;
+                        }
+                }
+        }
+
+        if (found > 0)
+                udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1");
+
+        /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
+         * those, consider it a full keyboard; do not test KEY_RESERVED, though */
+        mask = 0xFFFFFFFE;
+        if ((bitmask_key[0] & mask) == mask)
+                udev_builtin_add_property(dev, test, "ID_INPUT_KEYBOARD", "1");
+}
+
+static int builtin_input_id(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        struct udev_device *pdev;
+        unsigned long bitmask_ev[NBITS(EV_MAX)];
+        unsigned long bitmask_abs[NBITS(ABS_MAX)];
+        unsigned long bitmask_key[NBITS(KEY_MAX)];
+        unsigned long bitmask_rel[NBITS(REL_MAX)];
+
+        /* walk up the parental chain until we find the real input device; the
+         * argument is very likely a subdevice of this, like eventN */
+        pdev = dev;
+        while (pdev != NULL && udev_device_get_sysattr_value(pdev, "capabilities/ev") == NULL)
+                pdev = udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
+
+        /* not an "input" class device */
+        if (pdev == NULL)
+                return EXIT_SUCCESS;
+
+        /* Use this as a flag that input devices were detected, so that this
+         * program doesn't need to be called more than once per device */
+        udev_builtin_add_property(dev, test, "ID_INPUT", "1");
+        get_cap_mask(dev, pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test);
+        get_cap_mask(dev, pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test);
+        get_cap_mask(dev, pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test);
+        get_cap_mask(dev, pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test);
+        test_pointers(dev, bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel, test);
+        test_key(dev, bitmask_ev, bitmask_key, test);
+        return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_input_id = {
+        .name = "input_id",
+        .cmd = builtin_input_id,
+        .help = "input device properties",
+};
diff --git a/src/udev-builtin-kmod.c b/src/udev-builtin-kmod.c
new file mode 100644
index 0000000..57e813f
--- /dev/null
+++ b/src/udev-builtin-kmod.c
@@ -0,0 +1,142 @@
+/*
+ * load kernel modules
+ *
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2011 ProFUSION embedded systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <libkmod.h>
+
+#include "udev.h"
+
+static struct kmod_ctx *ctx;
+
+static int load_module(struct udev *udev, const char *alias)
+{
+        struct kmod_list *list = NULL;
+        struct kmod_list *l;
+        int err;
+
+        err = kmod_module_new_from_lookup(ctx, alias, &list);
+        if (err < 0)
+                return err;
+
+        if (list == NULL)
+                info(udev, "no module matches '%s'\n", alias);
+
+        kmod_list_foreach(l, list) {
+                struct kmod_module *mod = kmod_module_get_module(l);
+
+                err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL);
+                if (err == KMOD_PROBE_APPLY_BLACKLIST)
+                        info(udev, "module '%s' is blacklisted\n", kmod_module_get_name(mod));
+                else if (err == 0)
+                        info(udev, "inserted '%s'\n", kmod_module_get_name(mod));
+                else
+                        info(udev, "failed to insert '%s'\n", kmod_module_get_name(mod));
+
+                kmod_module_unref(mod);
+        }
+
+        kmod_module_unref_list(list);
+        return err;
+}
+
+static void udev_kmod_log(void *data, int priority, const char *file, int line,
+                          const char *fn, const char *format, va_list args)
+{
+        udev_main_log(data, priority, file, line, fn, format, args);
+}
+
+/* needs to re-instantiate the context after a reload */
+static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        int i;
+
+        if (!ctx) {
+                ctx = kmod_new(NULL, NULL);
+                if (!ctx)
+                        return -ENOMEM;
+
+                info(udev, "load module index\n");
+                kmod_set_log_fn(ctx, udev_kmod_log, udev);
+                kmod_load_resources(ctx);
+        }
+
+        if (argc < 3 || strcmp(argv[1], "load")) {
+                err(udev, "expect: %s load <module>\n", argv[0]);
+                return EXIT_FAILURE;
+        }
+
+        for (i = 2; argv[i]; i++) {
+                info(udev, "execute '%s' '%s'\n", argv[1], argv[i]);
+                load_module(udev, argv[i]);
+        }
+
+        return EXIT_SUCCESS;
+}
+
+/* called at udev startup */
+static int builtin_kmod_init(struct udev *udev)
+{
+        if (ctx)
+                return 0;
+
+        ctx = kmod_new(NULL, NULL);
+        if (!ctx)
+                return -ENOMEM;
+
+        info(udev, "load module index\n");
+        kmod_set_log_fn(ctx, udev_kmod_log, udev);
+        kmod_load_resources(ctx);
+        return 0;
+}
+
+/* called on udev shutdown and reload request */
+static void builtin_kmod_exit(struct udev *udev)
+{
+        info(udev, "unload module index\n");
+        ctx = kmod_unref(ctx);
+}
+
+/* called every couple of seconds during event activity; 'true' if config has changed */
+static bool builtin_kmod_validate(struct udev *udev)
+{
+        info(udev, "validate module index\n");
+        if (kmod_validate_resources(ctx) != KMOD_RESOURCES_OK)
+                return true;
+        return false;
+}
+
+const struct udev_builtin udev_builtin_kmod = {
+        .name = "kmod",
+        .cmd = builtin_kmod,
+        .init = builtin_kmod_init,
+        .exit = builtin_kmod_exit,
+        .validate = builtin_kmod_validate,
+        .help = "kernel module loader",
+        .run_once = false,
+};
diff --git a/src/udev-builtin-path_id.c b/src/udev-builtin-path_id.c
new file mode 100644
index 0000000..a8559d2
--- /dev/null
+++ b/src/udev-builtin-path_id.c
@@ -0,0 +1,498 @@
+/*
+ * compose persistent device path
+ *
+ * Copyright (C) 2009-2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * Logic based on Hannes Reinecke's shell script.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "udev.h"
+
+static int path_prepend(char **path, const char *fmt, ...)
+{
+        va_list va;
+        char *pre;
+        int err = 0;
+
+        va_start(va, fmt);
+        err = vasprintf(&pre, fmt, va);
+        va_end(va);
+        if (err < 0)
+                goto out;
+
+        if (*path != NULL) {
+                char *new;
+
+                err = asprintf(&new, "%s-%s", pre, *path);
+                free(pre);
+                if (err < 0)
+                        goto out;
+                free(*path);
+                *path = new;
+        } else {
+                *path = pre;
+        }
+out:
+        return err;
+}
+
+/*
+** Linux only supports 32 bit luns.
+** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
+*/
+static int format_lun_number(struct udev_device *dev, char **path)
+{
+        unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10);
+
+        /* address method 0, peripheral device addressing with bus id of zero */
+        if (lun < 256)
+                return path_prepend(path, "lun-%d", lun);
+        /* handle all other lun addressing methods by using a variant of the original lun format */
+        return path_prepend(path, "lun-0x%04x%04x00000000", (lun & 0xffff), (lun >> 16) & 0xffff);
+}
+
+static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys)
+{
+        struct udev_device *parent = dev;
+
+        while (parent != NULL) {
+                const char *subsystem;
+
+                subsystem = udev_device_get_subsystem(parent);
+                if (subsystem == NULL || strcmp(subsystem, subsys) != 0)
+                        break;
+                dev = parent;
+                parent = udev_device_get_parent(parent);
+        }
+        return dev;
+}
+
+static struct udev_device *handle_scsi_fibre_channel(struct udev_device *parent, char **path)
+{
+        struct udev *udev  = udev_device_get_udev(parent);
+        struct udev_device *targetdev;
+        struct udev_device *fcdev = NULL;
+        const char *port;
+        char *lun = NULL;;
+
+        targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
+        if (targetdev == NULL)
+                return NULL;
+
+        fcdev = udev_device_new_from_subsystem_sysname(udev, "fc_transport", udev_device_get_sysname(targetdev));
+        if (fcdev == NULL)
+                return NULL;
+        port = udev_device_get_sysattr_value(fcdev, "port_name");
+        if (port == NULL) {
+                parent = NULL;
+                goto out;
+        }
+
+        format_lun_number(parent, &lun);
+        path_prepend(path, "fc-%s-%s", port, lun);
+        if (lun)
+                free(lun);
+out:
+        udev_device_unref(fcdev);
+        return parent;
+}
+
+static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path)
+{
+        struct udev *udev  = udev_device_get_udev(parent);
+        struct udev_device *targetdev;
+        struct udev_device *target_parent;
+        struct udev_device *sasdev;
+        const char *sas_address;
+        char *lun = NULL;
+
+        targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
+        if (targetdev == NULL)
+                return NULL;
+
+        target_parent = udev_device_get_parent(targetdev);
+        if (target_parent == NULL)
+                return NULL;
+
+        sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device",
+                                udev_device_get_sysname(target_parent));
+        if (sasdev == NULL)
+                return NULL;
+
+        sas_address = udev_device_get_sysattr_value(sasdev, "sas_address");
+        if (sas_address == NULL) {
+                parent = NULL;
+                goto out;
+        }
+
+        format_lun_number(parent, &lun);
+        path_prepend(path, "sas-%s-%s", sas_address, lun);
+        if (lun)
+                free(lun);
+out:
+        udev_device_unref(sasdev);
+        return parent;
+}
+
+static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path)
+{
+        struct udev *udev  = udev_device_get_udev(parent);
+        struct udev_device *transportdev;
+        struct udev_device *sessiondev = NULL;
+        const char *target;
+        char *connname;
+        struct udev_device *conndev = NULL;
+        const char *addr;
+        const char *port;
+        char *lun = NULL;
+
+        /* find iscsi session */
+        transportdev = parent;
+        for (;;) {
+                transportdev = udev_device_get_parent(transportdev);
+                if (transportdev == NULL)
+                        return NULL;
+                if (strncmp(udev_device_get_sysname(transportdev), "session", 7) == 0)
+                        break;
+        }
+
+        /* find iscsi session device */
+        sessiondev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", udev_device_get_sysname(transportdev));
+        if (sessiondev == NULL)
+                return NULL;
+        target = udev_device_get_sysattr_value(sessiondev, "targetname");
+        if (target == NULL) {
+                parent = NULL;
+                goto out;
+        }
+
+        if (asprintf(&connname, "connection%s:0", udev_device_get_sysnum(transportdev)) < 0) {
+                parent = NULL;
+                goto out;
+        }
+        conndev = udev_device_new_from_subsystem_sysname(udev, "iscsi_connection", connname);
+        free(connname);
+        if (conndev == NULL) {
+                parent = NULL;
+                goto out;
+        }
+        addr = udev_device_get_sysattr_value(conndev, "persistent_address");
+        port = udev_device_get_sysattr_value(conndev, "persistent_port");
+        if (addr == NULL || port == NULL) {
+                parent = NULL;
+                goto out;
+        }
+
+        format_lun_number(parent, &lun);
+        path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun);
+        if (lun)
+                free(lun);
+out:
+        udev_device_unref(sessiondev);
+        udev_device_unref(conndev);
+        return parent;
+}
+
+static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path)
+{
+        struct udev_device *hostdev;
+        int host, bus, target, lun;
+        const char *name;
+        char *base;
+        char *pos;
+        DIR *dir;
+        struct dirent *dent;
+        int basenum;
+
+        hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
+        if (hostdev == NULL)
+                return NULL;
+
+        name = udev_device_get_sysname(parent);
+        if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4)
+                return NULL;
+
+        /* rebase host offset to get the local relative number */
+        basenum = -1;
+        base = strdup(udev_device_get_syspath(hostdev));
+        if (base == NULL)
+                return NULL;
+        pos = strrchr(base, '/');
+        if (pos == NULL) {
+                parent = NULL;
+                goto out;
+        }
+        pos[0] = '\0';
+        dir = opendir(base);
+        if (dir == NULL) {
+                parent = NULL;
+                goto out;
+        }
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                char *rest;
+                int i;
+
+                if (dent->d_name[0] == '.')
+                        continue;
+                if (dent->d_type != DT_DIR && dent->d_type != DT_LNK)
+                        continue;
+                if (strncmp(dent->d_name, "host", 4) != 0)
+                        continue;
+                i = strtoul(&dent->d_name[4], &rest, 10);
+                if (rest[0] != '\0')
+                        continue;
+                /*
+                 * find the smallest number; the host really needs to export its
+                 * own instance number per parent device; relying on the global host
+                 * enumeration and plainly rebasing the numbers sounds unreliable
+                 */
+                if (basenum == -1 || i < basenum)
+                        basenum = i;
+        }
+        closedir(dir);
+        if (basenum == -1) {
+                parent = NULL;
+                goto out;
+        }
+        host -= basenum;
+
+        path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun);
+out:
+        free(base);
+        return hostdev;
+}
+
+static struct udev_device *handle_scsi(struct udev_device *parent, char **path)
+{
+        const char *devtype;
+        const char *name;
+        const char *id;
+
+        devtype = udev_device_get_devtype(parent);
+        if (devtype == NULL || strcmp(devtype, "scsi_device") != 0)
+                return parent;
+
+        /* firewire */
+        id = udev_device_get_sysattr_value(parent, "ieee1394_id");
+        if (id != NULL) {
+                parent = skip_subsystem(parent, "scsi");
+                path_prepend(path, "ieee1394-0x%s", id);
+                goto out;
+        }
+
+        /* lousy scsi sysfs does not have a "subsystem" for the transport */
+        name = udev_device_get_syspath(parent);
+
+        if (strstr(name, "/rport-") != NULL) {
+                parent = handle_scsi_fibre_channel(parent, path);
+                goto out;
+        }
+
+        if (strstr(name, "/end_device-") != NULL) {
+                parent = handle_scsi_sas(parent, path);
+                goto out;
+        }
+
+        if (strstr(name, "/session") != NULL) {
+                parent = handle_scsi_iscsi(parent, path);
+                goto out;
+        }
+
+        /*
+         * We do not support the ATA transport class, it creates duplicated link
+         * names as the fake SCSI host adapters are all separated, they are all
+         * re-based as host == 0. ATA should just stop faking two duplicated
+         * hierarchies for a single topology and leave the SCSI stuff alone;
+         * until that happens, there are no by-path/ links for ATA devices behind
+         * an ATA transport class.
+         */
+        if (strstr(name, "/ata") != NULL) {
+                parent = NULL;
+                goto out;
+        }
+
+        parent = handle_scsi_default(parent, path);
+out:
+        return parent;
+}
+
+static void handle_scsi_tape(struct udev_device *dev, char **path)
+{
+        const char *name;
+
+        /* must be the last device in the syspath */
+        if (*path != NULL)
+                return;
+
+        name = udev_device_get_sysname(dev);
+        if (strncmp(name, "nst", 3) == 0 && strchr("lma", name[3]) != NULL)
+                path_prepend(path, "nst%c", name[3]);
+        else if (strncmp(name, "st", 2) == 0 && strchr("lma", name[2]) != NULL)
+                path_prepend(path, "st%c", name[2]);
+}
+
+static struct udev_device *handle_usb(struct udev_device *parent, char **path)
+{
+        const char *devtype;
+        const char *str;
+        const char *port;
+
+        devtype = udev_device_get_devtype(parent);
+        if (devtype == NULL)
+                return parent;
+        if (strcmp(devtype, "usb_interface") != 0 && strcmp(devtype, "usb_device") != 0)
+                return parent;
+
+        str = udev_device_get_sysname(parent);
+        port = strchr(str, '-');
+        if (port == NULL)
+                return parent;
+        port++;
+
+        parent = skip_subsystem(parent, "usb");
+        path_prepend(path, "usb-0:%s", port);
+        return parent;
+}
+
+static struct udev_device *handle_ccw(struct udev_device *parent, struct udev_device *dev, char **path)
+{
+        struct udev_device *scsi_dev;
+
+        scsi_dev = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device");
+        if (scsi_dev != NULL) {
+                const char *wwpn;
+                const char *lun;
+                const char *hba_id;
+
+                hba_id = udev_device_get_sysattr_value(scsi_dev, "hba_id");
+                wwpn = udev_device_get_sysattr_value(scsi_dev, "wwpn");
+                lun = udev_device_get_sysattr_value(scsi_dev, "fcp_lun");
+                if (hba_id != NULL && lun != NULL && wwpn != NULL) {
+                        path_prepend(path, "ccw-%s-zfcp-%s:%s", hba_id, wwpn, lun);
+                        goto out;
+                }
+        }
+
+        path_prepend(path, "ccw-%s", udev_device_get_sysname(parent));
+out:
+        parent = skip_subsystem(parent, "ccw");
+        return parent;
+}
+
+static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        struct udev_device *parent;
+        char *path = NULL;
+
+        /* S390 ccw bus */
+        parent = udev_device_get_parent_with_subsystem_devtype(dev, "ccw", NULL);
+        if (parent != NULL) {
+                handle_ccw(parent, dev, &path);
+                goto out;
+        }
+
+        /* walk up the chain of devices and compose path */
+        parent = dev;
+        while (parent != NULL) {
+                const char *subsys;
+
+                subsys = udev_device_get_subsystem(parent);
+                if (subsys == NULL) {
+                        ;
+                } else if (strcmp(subsys, "scsi_tape") == 0) {
+                        handle_scsi_tape(parent, &path);
+                } else if (strcmp(subsys, "scsi") == 0) {
+                        parent = handle_scsi(parent, &path);
+                } else if (strcmp(subsys, "usb") == 0) {
+                        parent = handle_usb(parent, &path);
+                } else if (strcmp(subsys, "serio") == 0) {
+                        path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
+                        parent = skip_subsystem(parent, "serio");
+                } else if (strcmp(subsys, "pci") == 0) {
+                        path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
+                        parent = skip_subsystem(parent, "pci");
+                } else if (strcmp(subsys, "platform") == 0) {
+                        path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
+                        parent = skip_subsystem(parent, "platform");
+                } else if (strcmp(subsys, "acpi") == 0) {
+                        path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent));
+                        parent = skip_subsystem(parent, "acpi");
+                } else if (strcmp(subsys, "xen") == 0) {
+                        path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
+                        parent = skip_subsystem(parent, "xen");
+                } else if (strcmp(subsys, "virtio") == 0) {
+                        path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent));
+                        parent = skip_subsystem(parent, "virtio");
+                }
+
+                parent = udev_device_get_parent(parent);
+        }
+out:
+        if (path != NULL) {
+                char tag[UTIL_NAME_SIZE];
+                size_t i;
+                const char *p;
+
+                /* compose valid udev tag name */
+                for (p = path, i = 0; *p; p++) {
+                        if ((*p >= '0' && *p <= '9') ||
+                            (*p >= 'A' && *p <= 'Z') ||
+                            (*p >= 'a' && *p <= 'z') ||
+                            *p == '-') {
+                                tag[i++] = *p;
+                                continue;
+                        }
+
+                        /* skip all leading '_' */
+                        if (i == 0)
+                                continue;
+
+                        /* avoid second '_' */
+                        if (tag[i-1] == '_')
+                                continue;
+
+                        tag[i++] = '_';
+                }
+                /* strip trailing '_' */
+                while (i > 0 && tag[i-1] == '_')
+                        i--;
+                tag[i] = '\0';
+
+                udev_builtin_add_property(dev, test, "ID_PATH", path);
+                udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
+                free(path);
+                return EXIT_SUCCESS;
+        }
+        return EXIT_FAILURE;
+}
+
+const struct udev_builtin udev_builtin_path_id = {
+        .name = "path_id",
+        .cmd = builtin_path_id,
+        .help = "compose persistent device path",
+        .run_once = true,
+};
diff --git a/src/udev-builtin-usb_id.c b/src/udev-builtin-usb_id.c
new file mode 100644
index 0000000..85828e3
--- /dev/null
+++ b/src/udev-builtin-usb_id.c
@@ -0,0 +1,482 @@
+/*
+ * USB device properties and persistent device path
+ *
+ * Copyright (c) 2005 SUSE Linux Products GmbH, Germany
+ *   Author: Hannes Reinecke <hare@suse.de>
+ *
+ * Copyright (C) 2005-2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "udev.h"
+
+static void set_usb_iftype(char *to, int if_class_num, size_t len)
+{
+        char *type = "generic";
+
+        switch (if_class_num) {
+        case 1:
+                type = "audio";
+                break;
+        case 2: /* CDC-Control */
+                break;
+        case 3:
+                type = "hid";
+                break;
+        case 5: /* Physical */
+                break;
+        case 6:
+                type = "media";
+                break;
+        case 7:
+                type = "printer";
+                break;
+        case 8:
+                type = "storage";
+                break;
+        case 9:
+                type = "hub";
+                break;
+        case 0x0a: /* CDC-Data */
+                break;
+        case 0x0b: /* Chip/Smart Card */
+                break;
+        case 0x0d: /* Content Security */
+                break;
+        case 0x0e:
+                type = "video";
+                break;
+        case 0xdc: /* Diagnostic Device */
+                break;
+        case 0xe0: /* Wireless Controller */
+                break;
+        case 0xfe: /* Application-specific */
+                break;
+        case 0xff: /* Vendor-specific */
+                break;
+        default:
+                break;
+        }
+        strncpy(to, type, len);
+        to[len-1] = '\0';
+}
+
+static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len)
+{
+        int type_num = 0;
+        char *eptr;
+        char *type = "generic";
+
+        type_num = strtoul(from, &eptr, 0);
+        if (eptr != from) {
+                switch (type_num) {
+                case 2:
+                        type = "atapi";
+                        break;
+                case 3:
+                        type = "tape";
+                        break;
+                case 4: /* UFI */
+                case 5: /* SFF-8070i */
+                        type = "floppy";
+                        break;
+                case 1: /* RBC devices */
+                        type = "rbc";
+                        break;
+                case 6: /* Transparent SPC-2 devices */
+                        type = "scsi";
+                        break;
+                default:
+                        break;
+                }
+        }
+        util_strscpy(to, len, type);
+        return type_num;
+}
+
+static void set_scsi_type(char *to, const char *from, size_t len)
+{
+        int type_num;
+        char *eptr;
+        char *type = "generic";
+
+        type_num = strtoul(from, &eptr, 0);
+        if (eptr != from) {
+                switch (type_num) {
+                case 0:
+                case 0xe:
+                        type = "disk";
+                        break;
+                case 1:
+                        type = "tape";
+                        break;
+                case 4:
+                case 7:
+                case 0xf:
+                        type = "optical";
+                        break;
+                case 5:
+                        type = "cd";
+                        break;
+                default:
+                        break;
+                }
+        }
+        util_strscpy(to, len, type);
+}
+
+#define USB_DT_DEVICE                        0x01
+#define USB_DT_INTERFACE                0x04
+
+static int dev_if_packed_info(struct udev_device *dev, char *ifs_str, size_t len)
+{
+        char *filename = NULL;
+        int fd;
+        ssize_t size;
+        unsigned char buf[18 + 65535];
+        unsigned int pos, strpos;
+        struct usb_interface_descriptor {
+                u_int8_t        bLength;
+                u_int8_t        bDescriptorType;
+                u_int8_t        bInterfaceNumber;
+                u_int8_t        bAlternateSetting;
+                u_int8_t        bNumEndpoints;
+                u_int8_t        bInterfaceClass;
+                u_int8_t        bInterfaceSubClass;
+                u_int8_t        bInterfaceProtocol;
+                u_int8_t        iInterface;
+        } __attribute__((packed));
+        int err = 0;
+
+        if (asprintf(&filename, "%s/descriptors", udev_device_get_syspath(dev)) < 0) {
+                err = -1;
+                goto out;
+        }
+        fd = open(filename, O_RDONLY|O_CLOEXEC);
+        if (fd < 0) {
+                fprintf(stderr, "error opening USB device 'descriptors' file\n");
+                err = -1;
+                goto out;
+        }
+        size = read(fd, buf, sizeof(buf));
+        close(fd);
+        if (size < 18 || size == sizeof(buf)) {
+                err = -1;
+                goto out;
+        }
+
+        pos = 0;
+        strpos = 0;
+        ifs_str[0] = '\0';
+        while (pos < sizeof(buf) && strpos+7 < len-2) {
+                struct usb_interface_descriptor *desc;
+                char if_str[8];
+
+                desc = (struct usb_interface_descriptor *) &buf[pos];
+                if (desc->bLength < 3)
+                        break;
+                pos += desc->bLength;
+
+                if (desc->bDescriptorType != USB_DT_INTERFACE)
+                        continue;
+
+                if (snprintf(if_str, 8, ":%02x%02x%02x",
+                             desc->bInterfaceClass,
+                             desc->bInterfaceSubClass,
+                             desc->bInterfaceProtocol) != 7)
+                        continue;
+
+                if (strstr(ifs_str, if_str) != NULL)
+                        continue;
+
+                memcpy(&ifs_str[strpos], if_str, 8),
+                strpos += 7;
+        }
+        if (strpos > 0) {
+                ifs_str[strpos++] = ':';
+                ifs_str[strpos++] = '\0';
+        }
+out:
+        free(filename);
+        return err;
+}
+
+/*
+ * A unique USB identification is generated like this:
+ *
+ * 1.) Get the USB device type from InterfaceClass and InterfaceSubClass
+ * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC'
+ *     use the SCSI vendor and model as USB-Vendor and USB-model.
+ * 3.) Otherwise use the USB manufacturer and product as
+ *     USB-Vendor and USB-model. Any non-printable characters
+ *     in those strings will be skipped; a slash '/' will be converted
+ *     into a full stop '.'.
+ * 4.) If that fails, too, we will use idVendor and idProduct
+ *     as USB-Vendor and USB-model.
+ * 5.) The USB identification is the USB-vendor and USB-model
+ *     string concatenated with an underscore '_'.
+ * 6.) If the device supplies a serial number, this number
+ *     is concatenated with the identification with an underscore '_'.
+ */
+static int builtin_usb_id(struct udev_device *dev, int argc, char *argv[], bool test)
+{
+        char vendor_str[64];
+        char vendor_str_enc[256];
+        const char *vendor_id;
+        char model_str[64];
+        char model_str_enc[256];
+        const char *product_id;
+        char serial_str[UTIL_NAME_SIZE];
+        char packed_if_str[UTIL_NAME_SIZE];
+        char revision_str[64];
+        char type_str[64];
+        char instance_str[64];
+        const char *ifnum = NULL;
+        const char *driver = NULL;
+        char serial[256];
+
+        struct udev *udev = udev_device_get_udev(dev);
+        struct udev_device *dev_interface = NULL;
+        struct udev_device *dev_usb = NULL;
+        const char *if_class, *if_subclass;
+        int if_class_num;
+        int protocol = 0;
+        size_t l;
+        char *s;
+
+        vendor_str[0] = '\0';
+        model_str[0] = '\0';
+        serial_str[0] = '\0';
+        packed_if_str[0] = '\0';
+        revision_str[0] = '\0';
+        type_str[0] = '\0';
+        instance_str[0] = '\0';
+
+        dbg(udev, "syspath %s\n", udev_device_get_syspath(dev));
+
+        /* shortcut, if we are called directly for a "usb_device" type */
+        if (udev_device_get_devtype(dev) != NULL && strcmp(udev_device_get_devtype(dev), "usb_device") == 0) {
+                dev_if_packed_info(dev, packed_if_str, sizeof(packed_if_str));
+                dev_usb = dev;
+                goto fallback;
+        }
+
+        /* usb interface directory */
+        dev_interface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface");
+        if (dev_interface == NULL) {
+                info(udev, "unable to access usb_interface device of '%s'\n",
+                     udev_device_get_syspath(dev));
+                return EXIT_FAILURE;
+        }
+
+        ifnum = udev_device_get_sysattr_value(dev_interface, "bInterfaceNumber");
+        driver = udev_device_get_sysattr_value(dev_interface, "driver");
+
+        if_class = udev_device_get_sysattr_value(dev_interface, "bInterfaceClass");
+        if (!if_class) {
+                info(udev, "%s: cannot get bInterfaceClass attribute\n",
+                     udev_device_get_sysname(dev));
+                return EXIT_FAILURE;
+        }
+
+        if_class_num = strtoul(if_class, NULL, 16);
+        if (if_class_num == 8) {
+                /* mass storage */
+                if_subclass = udev_device_get_sysattr_value(dev_interface, "bInterfaceSubClass");
+                if (if_subclass != NULL)
+                        protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1);
+        } else {
+                set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1);
+        }
+
+        info(udev, "%s: if_class %d protocol %d\n",
+             udev_device_get_syspath(dev_interface), if_class_num, protocol);
+
+        /* usb device directory */
+        dev_usb = udev_device_get_parent_with_subsystem_devtype(dev_interface, "usb", "usb_device");
+        if (!dev_usb) {
+                info(udev, "unable to find parent 'usb' device of '%s'\n",
+                     udev_device_get_syspath(dev));
+                return EXIT_FAILURE;
+        }
+
+        /* all interfaces of the device in a single string */
+        dev_if_packed_info(dev_usb, packed_if_str, sizeof(packed_if_str));
+
+        /* mass storage : SCSI or ATAPI */
+        if ((protocol == 6 || protocol == 2)) {
+                struct udev_device *dev_scsi;
+                const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev;
+                int host, bus, target, lun;
+
+                /* get scsi device */
+                dev_scsi = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device");
+                if (dev_scsi == NULL) {
+                        info(udev, "unable to find parent 'scsi' device of '%s'\n",
+                             udev_device_get_syspath(dev));
+                        goto fallback;
+                }
+                if (sscanf(udev_device_get_sysname(dev_scsi), "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) {
+                        info(udev, "invalid scsi device '%s'\n", udev_device_get_sysname(dev_scsi));
+                        goto fallback;
+                }
+
+                /* Generic SPC-2 device */
+                scsi_vendor = udev_device_get_sysattr_value(dev_scsi, "vendor");
+                if (!scsi_vendor) {
+                        info(udev, "%s: cannot get SCSI vendor attribute\n",
+                             udev_device_get_sysname(dev_scsi));
+                        goto fallback;
+                }
+                udev_util_encode_string(scsi_vendor, vendor_str_enc, sizeof(vendor_str_enc));
+                util_replace_whitespace(scsi_vendor, vendor_str, sizeof(vendor_str)-1);
+                util_replace_chars(vendor_str, NULL);
+
+                scsi_model = udev_device_get_sysattr_value(dev_scsi, "model");
+                if (!scsi_model) {
+                        info(udev, "%s: cannot get SCSI model attribute\n",
+                             udev_device_get_sysname(dev_scsi));
+                        goto fallback;
+                }
+                udev_util_encode_string(scsi_model, model_str_enc, sizeof(model_str_enc));
+                util_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1);
+                util_replace_chars(model_str, NULL);
+
+                scsi_type = udev_device_get_sysattr_value(dev_scsi, "type");
+                if (!scsi_type) {
+                        info(udev, "%s: cannot get SCSI type attribute\n",
+                             udev_device_get_sysname(dev_scsi));
+                        goto fallback;
+                }
+                set_scsi_type(type_str, scsi_type, sizeof(type_str)-1);
+
+                scsi_rev = udev_device_get_sysattr_value(dev_scsi, "rev");
+                if (!scsi_rev) {
+                        info(udev, "%s: cannot get SCSI revision attribute\n",
+                             udev_device_get_sysname(dev_scsi));
+                        goto fallback;
+                }
+                util_replace_whitespace(scsi_rev, revision_str, sizeof(revision_str)-1);
+                util_replace_chars(revision_str, NULL);
+
+                /*
+                 * some broken devices have the same identifiers
+                 * for all luns, export the target:lun number
+                 */
+                sprintf(instance_str, "%d:%d", target, lun);
+        }
+
+fallback:
+        vendor_id = udev_device_get_sysattr_value(dev_usb, "idVendor");
+        product_id = udev_device_get_sysattr_value(dev_usb, "idProduct");
+
+        /* fallback to USB vendor & device */
+        if (vendor_str[0] == '\0') {
+                const char *usb_vendor = NULL;
+
+                usb_vendor = udev_device_get_sysattr_value(dev_usb, "manufacturer");
+                if (!usb_vendor)
+                        usb_vendor = vendor_id;
+                if (!usb_vendor) {
+                        info(udev, "No USB vendor information available\n");
+                        return EXIT_FAILURE;
+                }
+                udev_util_encode_string(usb_vendor, vendor_str_enc, sizeof(vendor_str_enc));
+                util_replace_whitespace(usb_vendor, vendor_str, sizeof(vendor_str)-1);
+                util_replace_chars(vendor_str, NULL);
+        }
+
+        if (model_str[0] == '\0') {
+                const char *usb_model = NULL;
+
+                usb_model = udev_device_get_sysattr_value(dev_usb, "product");
+                if (!usb_model)
+                        usb_model = product_id;
+                if (!usb_model) {
+                        dbg(udev, "No USB model information available\n");
+                        return EXIT_FAILURE;
+                }
+                udev_util_encode_string(usb_model, model_str_enc, sizeof(model_str_enc));
+                util_replace_whitespace(usb_model, model_str, sizeof(model_str)-1);
+                util_replace_chars(model_str, NULL);
+        }
+
+        if (revision_str[0] == '\0') {
+                const char *usb_rev;
+
+                usb_rev = udev_device_get_sysattr_value(dev_usb, "bcdDevice");
+                if (usb_rev) {
+                        util_replace_whitespace(usb_rev, revision_str, sizeof(revision_str)-1);
+                        util_replace_chars(revision_str, NULL);
+                }
+        }
+
+        if (serial_str[0] == '\0') {
+                const char *usb_serial;
+
+                usb_serial = udev_device_get_sysattr_value(dev_usb, "serial");
+                if (usb_serial) {
+                        util_replace_whitespace(usb_serial, serial_str, sizeof(serial_str)-1);
+                        util_replace_chars(serial_str, NULL);
+                }
+        }
+
+        s = serial;
+        l = util_strpcpyl(&s, sizeof(serial), vendor_str, "_", model_str, NULL);
+        if (serial_str[0] != '\0')
+                l = util_strpcpyl(&s, l, "_", serial_str, NULL);
+
+        if (instance_str[0] != '\0')
+                util_strpcpyl(&s, l, "-", instance_str, NULL);
+
+        udev_builtin_add_property(dev, test, "ID_VENDOR", vendor_str);
+        udev_builtin_add_property(dev, test, "ID_VENDOR_ENC", vendor_str_enc);
+        udev_builtin_add_property(dev, test, "ID_VENDOR_ID", vendor_id);
+        udev_builtin_add_property(dev, test, "ID_MODEL", model_str);
+        udev_builtin_add_property(dev, test, "ID_MODEL_ENC", model_str_enc);
+        udev_builtin_add_property(dev, test, "ID_MODEL_ID", product_id);
+        udev_builtin_add_property(dev, test, "ID_REVISION", revision_str);
+        udev_builtin_add_property(dev, test, "ID_SERIAL", serial);
+        if (serial_str[0] != '\0')
+                udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str);
+        if (type_str[0] != '\0')
+                udev_builtin_add_property(dev, test, "ID_TYPE", type_str);
+        if (instance_str[0] != '\0')
+                udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str);
+        udev_builtin_add_property(dev, test, "ID_BUS", "usb");
+        if (packed_if_str[0] != '\0')
+                udev_builtin_add_property(dev, test, "ID_USB_INTERFACES", packed_if_str);
+        if (ifnum != NULL)
+                udev_builtin_add_property(dev, test, "ID_USB_INTERFACE_NUM", ifnum);
+        if (driver != NULL)
+                udev_builtin_add_property(dev, test, "ID_USB_DRIVER", driver);
+        return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_usb_id = {
+        .name = "usb_id",
+        .cmd = builtin_usb_id,
+        .help = "usb device properties",
+        .run_once = true,
+};
diff --git a/src/udev-builtin.c b/src/udev-builtin.c
new file mode 100644
index 0000000..5bc5fa6
--- /dev/null
+++ b/src/udev-builtin.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2007-2009 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include "udev.h"
+
+static const struct udev_builtin *builtins[] = {
+        [UDEV_BUILTIN_BLKID] = &udev_builtin_blkid,
+        [UDEV_BUILTIN_FIRMWARE] = &udev_builtin_firmware,
+        [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id,
+        [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod,
+        [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id,
+        [UDEV_BUILTIN_PCI_DB] = &udev_builtin_pci_db,
+        [UDEV_BUILTIN_USB_DB] = &udev_builtin_usb_db,
+        [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id,
+};
+
+int udev_builtin_init(struct udev *udev)
+{
+        unsigned int i;
+        int err;
+
+        for (i = 0; i < ARRAY_SIZE(builtins); i++) {
+                if (builtins[i]->init) {
+                        err = builtins[i]->init(udev);
+                        if (err < 0)
+                                break;
+                }
+        }
+        return err;
+}
+
+void udev_builtin_exit(struct udev *udev)
+{
+        unsigned int i;
+
+        for (i = 0; i < ARRAY_SIZE(builtins); i++)
+                if (builtins[i]->exit)
+                        builtins[i]->exit(udev);
+}
+
+bool udev_builtin_validate(struct udev *udev)
+{
+        unsigned int i;
+        bool change = false;
+
+        for (i = 0; i < ARRAY_SIZE(builtins); i++)
+                if (builtins[i]->validate)
+                        if (builtins[i]->validate(udev))
+                                change = true;
+        return change;
+}
+
+void udev_builtin_list(struct udev *udev)
+{
+        unsigned int i;
+
+        for (i = 0; i < ARRAY_SIZE(builtins); i++)
+                fprintf(stderr, "  %-12s %s\n", builtins[i]->name, builtins[i]->help);
+}
+
+const char *udev_builtin_name(enum udev_builtin_cmd cmd)
+{
+        return builtins[cmd]->name;
+}
+
+bool udev_builtin_run_once(enum udev_builtin_cmd cmd)
+{
+        return builtins[cmd]->run_once;
+}
+
+enum udev_builtin_cmd udev_builtin_lookup(const char *command)
+{
+        char name[UTIL_PATH_SIZE];
+        enum udev_builtin_cmd i;
+        char *pos;
+
+        util_strscpy(name, sizeof(name), command);
+        pos = strchr(name, ' ');
+        if (pos)
+                pos[0] = '\0';
+        for (i = 0; i < ARRAY_SIZE(builtins); i++)
+                if (strcmp(builtins[i]->name, name) == 0)
+                        return i;
+        return UDEV_BUILTIN_MAX;
+}
+
+int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test)
+{
+        char arg[UTIL_PATH_SIZE];
+        int argc;
+        char *argv[128];
+
+        optind = 0;
+        util_strscpy(arg, sizeof(arg), command);
+        udev_build_argv(udev_device_get_udev(dev), arg, &argc, argv);
+        return builtins[cmd]->cmd(dev, argc, argv, test);
+}
+
+int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val)
+{
+        struct udev_list_entry *entry;
+
+        entry = udev_device_add_property(dev, key, val);
+        /* store in db, skip private keys */
+        if (key[0] != '.')
+                udev_list_entry_set_num(entry, true);
+
+        info(udev_device_get_udev(dev), "%s=%s\n", key, val);
+        if (test)
+                printf("%s=%s\n", key, val);
+        return 0;
+}
diff --git a/src/udev-control.socket b/src/udev-control.socket
new file mode 100644
index 0000000..f80f774
--- /dev/null
+++ b/src/udev-control.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=udev Control Socket
+DefaultDependencies=no
+ConditionCapability=CAP_MKNOD
+
+[Socket]
+Service=udev.service
+ListenSequentialPacket=/run/udev/control
+SocketMode=0600
+PassCredentials=yes
diff --git a/src/udev-ctrl.c b/src/udev-ctrl.c
new file mode 100644
index 0000000..5556f1a
--- /dev/null
+++ b/src/udev-ctrl.c
@@ -0,0 +1,494 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "udev.h"
+
+/* wire protocol magic must match */
+#define UDEV_CTRL_MAGIC                                0xdead1dea
+
+enum udev_ctrl_msg_type {
+        UDEV_CTRL_UNKNOWN,
+        UDEV_CTRL_SET_LOG_LEVEL,
+        UDEV_CTRL_STOP_EXEC_QUEUE,
+        UDEV_CTRL_START_EXEC_QUEUE,
+        UDEV_CTRL_RELOAD,
+        UDEV_CTRL_SET_ENV,
+        UDEV_CTRL_SET_CHILDREN_MAX,
+        UDEV_CTRL_PING,
+        UDEV_CTRL_EXIT,
+};
+
+struct udev_ctrl_msg_wire {
+        char version[16];
+        unsigned int magic;
+        enum udev_ctrl_msg_type type;
+        union {
+                int intval;
+                char buf[256];
+        };
+};
+
+struct udev_ctrl_msg {
+        int refcount;
+        struct udev_ctrl_connection *conn;
+        struct udev_ctrl_msg_wire ctrl_msg_wire;
+};
+
+struct udev_ctrl {
+        int refcount;
+        struct udev *udev;
+        int sock;
+        struct sockaddr_un saddr;
+        socklen_t addrlen;
+        bool bound;
+        bool cleanup_socket;
+        bool connected;
+};
+
+struct udev_ctrl_connection {
+        int refcount;
+        struct udev_ctrl *uctrl;
+        int sock;
+};
+
+struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd)
+{
+        struct udev_ctrl *uctrl;
+
+        uctrl = calloc(1, sizeof(struct udev_ctrl));
+        if (uctrl == NULL)
+                return NULL;
+        uctrl->refcount = 1;
+        uctrl->udev = udev;
+
+        if (fd < 0) {
+                uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+                if (uctrl->sock < 0) {
+                        err(udev, "error getting socket: %m\n");
+                        udev_ctrl_unref(uctrl);
+                        return NULL;
+                }
+        } else {
+                uctrl->bound = true;
+                uctrl->sock = fd;
+        }
+
+        uctrl->saddr.sun_family = AF_LOCAL;
+        util_strscpyl(uctrl->saddr.sun_path, sizeof(uctrl->saddr.sun_path),
+                      udev_get_run_path(udev), "/control", NULL);
+        uctrl->addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(uctrl->saddr.sun_path);
+        return uctrl;
+}
+
+struct udev_ctrl *udev_ctrl_new(struct udev *udev)
+{
+        return udev_ctrl_new_from_fd(udev, -1);
+}
+
+int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl)
+{
+        int err;
+
+        if (!uctrl->bound) {
+                err = bind(uctrl->sock, (struct sockaddr *)&uctrl->saddr, uctrl->addrlen);
+                if (err < 0 && errno == EADDRINUSE) {
+                        unlink(uctrl->saddr.sun_path);
+                        err = bind(uctrl->sock, (struct sockaddr *)&uctrl->saddr, uctrl->addrlen);
+                }
+
+                if (err < 0) {
+                        err = -errno;
+                        err(uctrl->udev, "bind failed: %m\n");
+                        return err;
+                }
+
+                err = listen(uctrl->sock, 0);
+                if (err < 0) {
+                        err = -errno;
+                        err(uctrl->udev, "listen failed: %m\n");
+                        return err;
+                }
+
+                uctrl->bound = true;
+                uctrl->cleanup_socket = true;
+        }
+        return 0;
+}
+
+struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl)
+{
+        return uctrl->udev;
+}
+
+struct udev_ctrl *udev_ctrl_ref(struct udev_ctrl *uctrl)
+{
+        if (uctrl == NULL)
+                return NULL;
+        uctrl->refcount++;
+        return uctrl;
+}
+
+struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl)
+{
+        if (uctrl == NULL)
+                return NULL;
+        uctrl->refcount--;
+        if (uctrl->refcount > 0)
+                return uctrl;
+        if (uctrl->sock >= 0)
+                close(uctrl->sock);
+        free(uctrl);
+        return NULL;
+}
+
+int udev_ctrl_cleanup(struct udev_ctrl *uctrl)
+{
+        if (uctrl == NULL)
+                return 0;
+        if (uctrl->cleanup_socket)
+                unlink(uctrl->saddr.sun_path);
+        return 0;
+}
+
+int udev_ctrl_get_fd(struct udev_ctrl *uctrl)
+{
+        if (uctrl == NULL)
+                return -EINVAL;
+        return uctrl->sock;
+}
+
+struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl)
+{
+        struct udev_ctrl_connection *conn;
+        struct ucred ucred;
+        socklen_t slen;
+        const int on = 1;
+
+        conn = calloc(1, sizeof(struct udev_ctrl_connection));
+        if (conn == NULL)
+                return NULL;
+        conn->refcount = 1;
+        conn->uctrl = uctrl;
+
+        conn->sock = accept4(uctrl->sock, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK);
+        if (conn->sock < 0) {
+                if (errno != EINTR)
+                        err(uctrl->udev, "unable to receive ctrl connection: %m\n");
+                goto err;
+        }
+
+        /* check peer credential of connection */
+        slen = sizeof(ucred);
+        if (getsockopt(conn->sock, SOL_SOCKET, SO_PEERCRED, &ucred, &slen) < 0) {
+                err(uctrl->udev, "unable to receive credentials of ctrl connection: %m\n");
+                goto err;
+        }
+        if (ucred.uid > 0) {
+                err(uctrl->udev, "sender uid=%i, message ignored\n", ucred.uid);
+                goto err;
+        }
+
+        /* enable receiving of the sender credentials in the messages */
+        setsockopt(conn->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+        udev_ctrl_ref(uctrl);
+        return conn;
+err:
+        if (conn->sock >= 0)
+                close(conn->sock);
+        free(conn);
+        return NULL;
+}
+
+struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn)
+{
+        if (conn == NULL)
+                return NULL;
+        conn->refcount++;
+        return conn;
+}
+
+struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn)
+{
+        if (conn == NULL)
+                return NULL;
+        conn->refcount--;
+        if (conn->refcount > 0)
+                return conn;
+        if (conn->sock >= 0)
+                close(conn->sock);
+        udev_ctrl_unref(conn->uctrl);
+        free(conn);
+        return NULL;
+}
+
+static int ctrl_send(struct udev_ctrl *uctrl, enum udev_ctrl_msg_type type, int intval, const char *buf, int timeout)
+{
+        struct udev_ctrl_msg_wire ctrl_msg_wire;
+        int err = 0;
+
+        memset(&ctrl_msg_wire, 0x00, sizeof(struct udev_ctrl_msg_wire));
+        strcpy(ctrl_msg_wire.version, "udev-" VERSION);
+        ctrl_msg_wire.magic = UDEV_CTRL_MAGIC;
+        ctrl_msg_wire.type = type;
+
+        if (buf != NULL)
+                util_strscpy(ctrl_msg_wire.buf, sizeof(ctrl_msg_wire.buf), buf);
+        else
+                ctrl_msg_wire.intval = intval;
+
+        if (!uctrl->connected) {
+                if (connect(uctrl->sock, (struct sockaddr *)&uctrl->saddr, uctrl->addrlen) < 0) {
+                        err = -errno;
+                        goto out;
+                }
+                uctrl->connected = true;
+        }
+        if (send(uctrl->sock, &ctrl_msg_wire, sizeof(ctrl_msg_wire), 0) < 0) {
+                err = -errno;
+                goto out;
+        }
+
+        /* wait for peer message handling or disconnect */
+        for (;;) {
+                struct pollfd pfd[1];
+                int r;
+
+                pfd[0].fd = uctrl->sock;
+                pfd[0].events = POLLIN;
+                r = poll(pfd, 1, timeout * 1000);
+                if (r  < 0) {
+                        if (errno == EINTR)
+                                continue;
+                        err = -errno;
+                        break;
+                }
+
+                if (r > 0 && pfd[0].revents & POLLERR) {
+                        err = -EIO;
+                        break;
+                }
+
+                if (r == 0)
+                        err = -ETIMEDOUT;
+                break;
+        }
+out:
+        return err;
+}
+
+int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_SET_LOG_LEVEL, priority, NULL, timeout);
+}
+
+int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_STOP_EXEC_QUEUE, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_START_EXEC_QUEUE, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_RELOAD, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_SET_ENV, 0, key, timeout);
+}
+
+int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_SET_CHILDREN_MAX, count, NULL, timeout);
+}
+
+int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_PING, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout)
+{
+        return ctrl_send(uctrl, UDEV_CTRL_EXIT, 0, NULL, timeout);
+}
+
+struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn)
+{
+        struct udev *udev = conn->uctrl->udev;
+        struct udev_ctrl_msg *uctrl_msg;
+        ssize_t size;
+        struct msghdr smsg;
+        struct cmsghdr *cmsg;
+        struct iovec iov;
+        struct ucred *cred;
+        char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+
+        uctrl_msg = calloc(1, sizeof(struct udev_ctrl_msg));
+        if (uctrl_msg == NULL)
+                return NULL;
+        uctrl_msg->refcount = 1;
+        uctrl_msg->conn = conn;
+        udev_ctrl_connection_ref(conn);
+
+        /* wait for the incoming message */
+        for(;;) {
+                struct pollfd pfd[1];
+                int r;
+
+                pfd[0].fd = conn->sock;
+                pfd[0].events = POLLIN;
+
+                r = poll(pfd, 1, 10000);
+                if (r  < 0) {
+                        if (errno == EINTR)
+                                continue;
+                        goto err;
+                } else if (r == 0) {
+                        err(udev, "timeout waiting for ctrl message\n");
+                        goto err;
+                } else {
+                        if (!(pfd[0].revents & POLLIN)) {
+                                err(udev, "ctrl connection error: %m\n");
+                                goto err;
+                        }
+                }
+
+                break;
+        }
+
+        iov.iov_base = &uctrl_msg->ctrl_msg_wire;
+        iov.iov_len = sizeof(struct udev_ctrl_msg_wire);
+        memset(&smsg, 0x00, sizeof(struct msghdr));
+        smsg.msg_iov = &iov;
+        smsg.msg_iovlen = 1;
+        smsg.msg_control = cred_msg;
+        smsg.msg_controllen = sizeof(cred_msg);
+        size = recvmsg(conn->sock, &smsg, 0);
+        if (size <  0) {
+                err(udev, "unable to receive ctrl message: %m\n");
+                goto err;
+        }
+        cmsg = CMSG_FIRSTHDR(&smsg);
+        cred = (struct ucred *) CMSG_DATA(cmsg);
+
+        if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+                err(udev, "no sender credentials received, message ignored\n");
+                goto err;
+        }
+
+        if (cred->uid != 0) {
+                err(udev, "sender uid=%i, message ignored\n", cred->uid);
+                goto err;
+        }
+
+        if (uctrl_msg->ctrl_msg_wire.magic != UDEV_CTRL_MAGIC) {
+                err(udev, "message magic 0x%08x doesn't match, ignore it\n", uctrl_msg->ctrl_msg_wire.magic);
+                goto err;
+        }
+
+        dbg(udev, "created ctrl_msg %p (%i)\n", uctrl_msg, uctrl_msg->ctrl_msg_wire.type);
+        return uctrl_msg;
+err:
+        udev_ctrl_msg_unref(uctrl_msg);
+        return NULL;
+}
+
+struct udev_ctrl_msg *udev_ctrl_msg_ref(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg == NULL)
+                return NULL;
+        ctrl_msg->refcount++;
+        return ctrl_msg;
+}
+
+struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg == NULL)
+                return NULL;
+        ctrl_msg->refcount--;
+        if (ctrl_msg->refcount > 0)
+                return ctrl_msg;
+        dbg(ctrl_msg->conn->uctrl->udev, "release ctrl_msg %p\n", ctrl_msg);
+        udev_ctrl_connection_unref(ctrl_msg->conn);
+        free(ctrl_msg);
+        return NULL;
+}
+
+int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_LOG_LEVEL)
+                return ctrl_msg->ctrl_msg_wire.intval;
+        return -1;
+}
+
+int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_STOP_EXEC_QUEUE)
+                return 1;
+        return -1;
+}
+
+int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_START_EXEC_QUEUE)
+                return 1;
+        return -1;
+}
+
+int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_RELOAD)
+                return 1;
+        return -1;
+}
+
+const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_ENV)
+                return ctrl_msg->ctrl_msg_wire.buf;
+        return NULL;
+}
+
+int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_CHILDREN_MAX)
+                return ctrl_msg->ctrl_msg_wire.intval;
+        return -1;
+}
+
+int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_PING)
+                return 1;
+        return -1;
+}
+
+int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg)
+{
+        if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_EXIT)
+                return 1;
+        return -1;
+}
diff --git a/src/udev-event.c b/src/udev-event.c
new file mode 100644
index 0000000..45dd77b
--- /dev/null
+++ b/src/udev-event.c
@@ -0,0 +1,1011 @@
+/*
+ * Copyright (C) 2003-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/prctl.h>
+#include <sys/poll.h>
+#include <sys/epoll.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/signalfd.h>
+#include <linux/sockios.h>
+
+#include "udev.h"
+
+struct udev_event *udev_event_new(struct udev_device *dev)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        struct udev_event *event;
+
+        event = calloc(1, sizeof(struct udev_event));
+        if (event == NULL)
+                return NULL;
+        event->dev = dev;
+        event->udev = udev;
+        udev_list_init(udev, &event->run_list, false);
+        event->fd_signal = -1;
+        event->birth_usec = now_usec();
+        event->timeout_usec = 30 * 1000 * 1000;
+        dbg(event->udev, "allocated event %p\n", event);
+        return event;
+}
+
+void udev_event_unref(struct udev_event *event)
+{
+        if (event == NULL)
+                return;
+        udev_list_cleanup(&event->run_list);
+        free(event->program_result);
+        free(event->name);
+        dbg(event->udev, "free event %p\n", event);
+        free(event);
+}
+
+size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size)
+{
+        struct udev_device *dev = event->dev;
+        enum subst_type {
+                SUBST_UNKNOWN,
+                SUBST_DEVNODE,
+                SUBST_ATTR,
+                SUBST_ENV,
+                SUBST_KERNEL,
+                SUBST_KERNEL_NUMBER,
+                SUBST_DRIVER,
+                SUBST_DEVPATH,
+                SUBST_ID,
+                SUBST_MAJOR,
+                SUBST_MINOR,
+                SUBST_RESULT,
+                SUBST_PARENT,
+                SUBST_NAME,
+                SUBST_LINKS,
+                SUBST_ROOT,
+                SUBST_SYS,
+        };
+        static const struct subst_map {
+                char *name;
+                char fmt;
+                enum subst_type type;
+        } map[] = {
+                { .name = "devnode",        .fmt = 'N',        .type = SUBST_DEVNODE },
+                { .name = "tempnode",        .fmt = 'N',        .type = SUBST_DEVNODE },
+                { .name = "attr",        .fmt = 's',        .type = SUBST_ATTR },
+                { .name = "sysfs",        .fmt = 's',        .type = SUBST_ATTR },
+                { .name = "env",        .fmt = 'E',        .type = SUBST_ENV },
+                { .name = "kernel",        .fmt = 'k',        .type = SUBST_KERNEL },
+                { .name = "number",        .fmt = 'n',        .type = SUBST_KERNEL_NUMBER },
+                { .name = "driver",        .fmt = 'd',        .type = SUBST_DRIVER },
+                { .name = "devpath",        .fmt = 'p',        .type = SUBST_DEVPATH },
+                { .name = "id",                .fmt = 'b',        .type = SUBST_ID },
+                { .name = "major",        .fmt = 'M',        .type = SUBST_MAJOR },
+                { .name = "minor",        .fmt = 'm',        .type = SUBST_MINOR },
+                { .name = "result",        .fmt = 'c',        .type = SUBST_RESULT },
+                { .name = "parent",        .fmt = 'P',        .type = SUBST_PARENT },
+                { .name = "name",        .fmt = 'D',        .type = SUBST_NAME },
+                { .name = "links",        .fmt = 'L',        .type = SUBST_LINKS },
+                { .name = "root",        .fmt = 'r',        .type = SUBST_ROOT },
+                { .name = "sys",        .fmt = 'S',        .type = SUBST_SYS },
+        };
+        const char *from;
+        char *s;
+        size_t l;
+
+        from = src;
+        s = dest;
+        l = size;
+
+        for (;;) {
+                enum subst_type type = SUBST_UNKNOWN;
+                char attrbuf[UTIL_PATH_SIZE];
+                char *attr = NULL;
+
+                while (from[0] != '\0') {
+                        if (from[0] == '$') {
+                                /* substitute named variable */
+                                unsigned int i;
+
+                                if (from[1] == '$') {
+                                        from++;
+                                        goto copy;
+                                }
+
+                                for (i = 0; i < ARRAY_SIZE(map); i++) {
+                                        if (strncmp(&from[1], map[i].name, strlen(map[i].name)) == 0) {
+                                                type = map[i].type;
+                                                from += strlen(map[i].name)+1;
+                                                dbg(event->udev, "will substitute format name '%s'\n", map[i].name);
+                                                goto subst;
+                                        }
+                                }
+                        } else if (from[0] == '%') {
+                                /* substitute format char */
+                                unsigned int i;
+
+                                if (from[1] == '%') {
+                                        from++;
+                                        goto copy;
+                                }
+
+                                for (i = 0; i < ARRAY_SIZE(map); i++) {
+                                        if (from[1] == map[i].fmt) {
+                                                type = map[i].type;
+                                                from += 2;
+                                                dbg(event->udev, "will substitute format char '%c'\n", map[i].fmt);
+                                                goto subst;
+                                        }
+                                }
+                        }
+copy:
+                        /* copy char */
+                        if (l == 0)
+                                goto out;
+                        s[0] = from[0];
+                        from++;
+                        s++;
+                        l--;
+                }
+
+                goto out;
+subst:
+                /* extract possible $format{attr} */
+                if (from[0] == '{') {
+                        unsigned int i;
+
+                        from++;
+                        for (i = 0; from[i] != '}'; i++) {
+                                if (from[i] == '\0') {
+                                        err(event->udev, "missing closing brace for format '%s'\n", src);
+                                        goto out;
+                                }
+                        }
+                        if (i >= sizeof(attrbuf))
+                                goto out;
+                        memcpy(attrbuf, from, i);
+                        attrbuf[i] = '\0';
+                        from += i+1;
+                        attr = attrbuf;
+                } else {
+                        attr = NULL;
+                }
+
+                switch (type) {
+                case SUBST_DEVPATH:
+                        l = util_strpcpy(&s, l, udev_device_get_devpath(dev));
+                        dbg(event->udev, "substitute devpath '%s'\n", udev_device_get_devpath(dev));
+                        break;
+                case SUBST_KERNEL:
+                        l = util_strpcpy(&s, l, udev_device_get_sysname(dev));
+                        dbg(event->udev, "substitute kernel name '%s'\n", udev_device_get_sysname(dev));
+                        break;
+                case SUBST_KERNEL_NUMBER:
+                        if (udev_device_get_sysnum(dev) == NULL)
+                                break;
+                        l = util_strpcpy(&s, l, udev_device_get_sysnum(dev));
+                        dbg(event->udev, "substitute kernel number '%s'\n", udev_device_get_sysnum(dev));
+                        break;
+                case SUBST_ID:
+                        if (event->dev_parent == NULL)
+                                break;
+                        l = util_strpcpy(&s, l, udev_device_get_sysname(event->dev_parent));
+                        dbg(event->udev, "substitute id '%s'\n", udev_device_get_sysname(event->dev_parent));
+                        break;
+                case SUBST_DRIVER: {
+                        const char *driver;
+
+                        if (event->dev_parent == NULL)
+                                break;
+
+                        driver = udev_device_get_driver(event->dev_parent);
+                        if (driver == NULL)
+                                break;
+                        l = util_strpcpy(&s, l, driver);
+                        dbg(event->udev, "substitute driver '%s'\n", driver);
+                        break;
+                }
+                case SUBST_MAJOR: {
+                        char num[UTIL_PATH_SIZE];
+
+                        sprintf(num, "%d", major(udev_device_get_devnum(dev)));
+                        l = util_strpcpy(&s, l, num);
+                        dbg(event->udev, "substitute major number '%s'\n", num);
+                        break;
+                }
+                case SUBST_MINOR: {
+                        char num[UTIL_PATH_SIZE];
+
+                        sprintf(num, "%d", minor(udev_device_get_devnum(dev)));
+                        l = util_strpcpy(&s, l, num);
+                        dbg(event->udev, "substitute minor number '%s'\n", num);
+                        break;
+                }
+                case SUBST_RESULT: {
+                        char *rest;
+                        int i;
+
+                        if (event->program_result == NULL)
+                                break;
+                        /* get part part of the result string */
+                        i = 0;
+                        if (attr != NULL)
+                                i = strtoul(attr, &rest, 10);
+                        if (i > 0) {
+                                char result[UTIL_PATH_SIZE];
+                                char tmp[UTIL_PATH_SIZE];
+                                char *cpos;
+
+                                dbg(event->udev, "request part #%d of result string\n", i);
+                                util_strscpy(result, sizeof(result), event->program_result);
+                                cpos = result;
+                                while (--i) {
+                                        while (cpos[0] != '\0' && !isspace(cpos[0]))
+                                                cpos++;
+                                        while (isspace(cpos[0]))
+                                                cpos++;
+                                }
+                                if (i > 0) {
+                                        err(event->udev, "requested part of result string not found\n");
+                                        break;
+                                }
+                                util_strscpy(tmp, sizeof(tmp), cpos);
+                                /* %{2+}c copies the whole string from the second part on */
+                                if (rest[0] != '+') {
+                                        cpos = strchr(tmp, ' ');
+                                        if (cpos)
+                                                cpos[0] = '\0';
+                                }
+                                l = util_strpcpy(&s, l, tmp);
+                                dbg(event->udev, "substitute part of result string '%s'\n", tmp);
+                        } else {
+                                l = util_strpcpy(&s, l, event->program_result);
+                                dbg(event->udev, "substitute result string '%s'\n", event->program_result);
+                        }
+                        break;
+                }
+                case SUBST_ATTR: {
+                        const char *value = NULL;
+                        char vbuf[UTIL_NAME_SIZE];
+                        size_t len;
+                        int count;
+
+                        if (attr == NULL) {
+                                err(event->udev, "missing file parameter for attr\n");
+                                break;
+                        }
+
+                        /* try to read the value specified by "[dmi/id]product_name" */
+                        if (util_resolve_subsys_kernel(event->udev, attr, vbuf, sizeof(vbuf), 1) == 0)
+                                value = vbuf;
+
+                        /* try to read the attribute the device */
+                        if (value == NULL)
+                                value = udev_device_get_sysattr_value(event->dev, attr);
+
+                        /* try to read the attribute of the parent device, other matches have selected */
+                        if (value == NULL && event->dev_parent != NULL && event->dev_parent != event->dev)
+                                value = udev_device_get_sysattr_value(event->dev_parent, attr);
+
+                        if (value == NULL)
+                                break;
+
+                        /* strip trailing whitespace, and replace unwanted characters */
+                        if (value != vbuf)
+                                util_strscpy(vbuf, sizeof(vbuf), value);
+                        len = strlen(vbuf);
+                        while (len > 0 && isspace(vbuf[--len]))
+                                vbuf[len] = '\0';
+                        count = util_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT);
+                        if (count > 0)
+                                info(event->udev, "%i character(s) replaced\n" , count);
+                        l = util_strpcpy(&s, l, vbuf);
+                        dbg(event->udev, "substitute sysfs value '%s'\n", vbuf);
+                        break;
+                }
+                case SUBST_PARENT: {
+                        struct udev_device *dev_parent;
+                        const char *devnode;
+
+                        dev_parent = udev_device_get_parent(event->dev);
+                        if (dev_parent == NULL)
+                                break;
+                        devnode = udev_device_get_devnode(dev_parent);
+                        if (devnode != NULL) {
+                                size_t devlen = strlen(udev_get_dev_path(event->udev))+1;
+
+                                l = util_strpcpy(&s, l, &devnode[devlen]);
+                                dbg(event->udev, "found parent '%s', got node name '%s'\n",
+                                    udev_device_get_syspath(dev_parent), &devnode[devlen]);
+                        }
+                        break;
+                }
+                case SUBST_DEVNODE:
+                        if (udev_device_get_devnode(dev) != NULL)
+                                l = util_strpcpy(&s, l, udev_device_get_devnode(dev));
+                        break;
+                case SUBST_NAME: {
+                        if (event->name != NULL) {
+                                l = util_strpcpy(&s, l, event->name);
+                                dbg(event->udev, "substitute custom node name '%s'\n", event->name);
+                        } else if (udev_device_get_devnode(dev) != NULL) {
+                                size_t devlen = strlen(udev_get_dev_path(event->udev))+1;
+
+                                l = util_strpcpy(&s, l, &udev_device_get_devnode(dev)[devlen]);
+                                dbg(event->udev, "substitute node name'%s'\n", &udev_device_get_devnode(dev)[devlen]);
+                        } else {
+                                l = util_strpcpy(&s, l, udev_device_get_sysname(dev));
+                                dbg(event->udev, "substitute device name'%s'\n", udev_device_get_sysname(dev));
+                        }
+                        break;
+                }
+                case SUBST_LINKS: {
+                        size_t devlen = strlen(udev_get_dev_path(event->udev))+1;
+                        struct udev_list_entry *list_entry;
+
+                        list_entry = udev_device_get_devlinks_list_entry(dev);
+                        if (list_entry == NULL)
+                                break;
+                        l = util_strpcpy(&s, l, &udev_list_entry_get_name(list_entry)[devlen]);
+                        udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry))
+                                l = util_strpcpyl(&s, l, " ", &udev_list_entry_get_name(list_entry)[devlen], NULL);
+                        break;
+                }
+                case SUBST_ROOT:
+                        l = util_strpcpy(&s, l, udev_get_dev_path(event->udev));
+                        dbg(event->udev, "substitute udev_root '%s'\n", udev_get_dev_path(event->udev));
+                        break;
+                case SUBST_SYS:
+                        l = util_strpcpy(&s, l, udev_get_sys_path(event->udev));
+                        dbg(event->udev, "substitute sys_path '%s'\n", udev_get_sys_path(event->udev));
+                        break;
+                case SUBST_ENV:
+                        if (attr == NULL) {
+                                dbg(event->udev, "missing attribute\n");
+                                break;
+                        } else {
+                                const char *value;
+
+                                value = udev_device_get_property_value(event->dev, attr);
+                                if (value == NULL)
+                                        break;
+                                dbg(event->udev, "substitute env '%s=%s'\n", attr, value);
+                                l = util_strpcpy(&s, l, value);
+                                break;
+                        }
+                default:
+                        err(event->udev, "unknown substitution type=%i\n", type);
+                        break;
+                }
+        }
+
+out:
+        s[0] = '\0';
+        dbg(event->udev, "'%s' -> '%s' (%zu)\n", src, dest, l);
+        return l;
+}
+
+static int spawn_exec(struct udev_event *event,
+                      const char *cmd, char *const argv[], char **envp, const sigset_t *sigmask,
+                      int fd_stdout, int fd_stderr)
+{
+        struct udev *udev = event->udev;
+        int err;
+        int fd;
+
+        /* discard child output or connect to pipe */
+        fd = open("/dev/null", O_RDWR);
+        if (fd >= 0) {
+                dup2(fd, STDIN_FILENO);
+                if (fd_stdout < 0)
+                        dup2(fd, STDOUT_FILENO);
+                if (fd_stderr < 0)
+                        dup2(fd, STDERR_FILENO);
+                close(fd);
+        } else {
+                err(udev, "open /dev/null failed: %m\n");
+        }
+
+        /* connect pipes to std{out,err} */
+        if (fd_stdout >= 0) {
+                dup2(fd_stdout, STDOUT_FILENO);
+                        close(fd_stdout);
+        }
+        if (fd_stderr >= 0) {
+                dup2(fd_stderr, STDERR_FILENO);
+                close(fd_stderr);
+        }
+
+        /* terminate child in case parent goes away */
+        prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+        /* restore original udev sigmask before exec */
+        if (sigmask)
+                sigprocmask(SIG_SETMASK, sigmask, NULL);
+
+        execve(argv[0], argv, envp);
+
+        /* exec failed */
+        err = -errno;
+        err(udev, "failed to execute '%s' '%s': %m\n", argv[0], cmd);
+        return err;
+}
+
+static void spawn_read(struct udev_event *event,
+                      const char *cmd,
+                      int fd_stdout, int fd_stderr,
+                      char *result, size_t ressize)
+{
+        struct udev *udev = event->udev;
+        size_t respos = 0;
+        int fd_ep = -1;
+        struct epoll_event ep_outpipe, ep_errpipe;
+
+        /* read from child if requested */
+        if (fd_stdout < 0 && fd_stderr < 0)
+                return;
+
+        fd_ep = epoll_create1(EPOLL_CLOEXEC);
+        if (fd_ep < 0) {
+                err(udev, "error creating epoll fd: %m\n");
+                goto out;
+        }
+
+        if (fd_stdout >= 0) {
+                memset(&ep_outpipe, 0, sizeof(struct epoll_event));
+                ep_outpipe.events = EPOLLIN;
+                ep_outpipe.data.ptr = &fd_stdout;
+                if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stdout, &ep_outpipe) < 0) {
+                        err(udev, "fail to add fd to epoll: %m\n");
+                        goto out;
+                }
+        }
+
+        if (fd_stderr >= 0) {
+                memset(&ep_errpipe, 0, sizeof(struct epoll_event));
+                ep_errpipe.events = EPOLLIN;
+                ep_errpipe.data.ptr = &fd_stderr;
+                if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stderr, &ep_errpipe) < 0) {
+                        err(udev, "fail to add fd to epoll: %m\n");
+                        goto out;
+                }
+        }
+
+        /* read child output */
+        while (fd_stdout >= 0 || fd_stderr >= 0) {
+                int timeout;
+                int fdcount;
+                struct epoll_event ev[4];
+                int i;
+
+                if (event->timeout_usec > 0) {
+                        unsigned long long age_usec;
+
+                        age_usec = now_usec() - event->birth_usec;
+                        if (age_usec >= event->timeout_usec) {
+                                err(udev, "timeout '%s'\n", cmd);
+                                goto out;
+                        }
+                        timeout = ((event->timeout_usec - age_usec) / 1000) + 1000;
+                } else {
+                        timeout = -1;
+                }
+
+                fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), timeout);
+                if (fdcount < 0) {
+                        if (errno == EINTR)
+                                continue;
+                        err(udev, "failed to poll: %m\n");
+                        goto out;
+                }
+                if (fdcount == 0) {
+                        err(udev, "timeout '%s'\n", cmd);
+                        goto out;
+                }
+
+                for (i = 0; i < fdcount; i++) {
+                        int *fd = (int *)ev[i].data.ptr;
+
+                        if (ev[i].events & EPOLLIN) {
+                                ssize_t count;
+                                char buf[4096];
+
+                                count = read(*fd, buf, sizeof(buf)-1);
+                                if (count <= 0)
+                                        continue;
+                                buf[count] = '\0';
+
+                                /* store stdout result */
+                                if (result != NULL && *fd == fd_stdout) {
+                                        if (respos + count < ressize) {
+                                                memcpy(&result[respos], buf, count);
+                                                respos += count;
+                                        } else {
+                                                err(udev, "'%s' ressize %zd too short\n", cmd, ressize);
+                                        }
+                                }
+
+                                /* log debug output only if we watch stderr */
+                                if (fd_stderr >= 0) {
+                                        char *pos;
+                                        char *line;
+
+                                        pos = buf;
+                                        while ((line = strsep(&pos, "\n"))) {
+                                                if (pos != NULL || line[0] != '\0')
+                                                        info(udev, "'%s'(%s) '%s'\n", cmd, *fd == fd_stdout ? "out" : "err" , line);
+                                        }
+                                }
+                        } else if (ev[i].events & EPOLLHUP) {
+                                if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, *fd, NULL) < 0) {
+                                        err(udev, "failed to remove fd from epoll: %m\n");
+                                        goto out;
+                                }
+                                *fd = -1;
+                        }
+                }
+        }
+
+        /* return the child's stdout string */
+        if (result != NULL) {
+                result[respos] = '\0';
+                dbg(udev, "result='%s'\n", result);
+        }
+out:
+        if (fd_ep >= 0)
+                close(fd_ep);
+}
+
+static int spawn_wait(struct udev_event *event, const char *cmd, pid_t pid)
+{
+        struct udev *udev = event->udev;
+        struct pollfd pfd[1];
+        int err = 0;
+
+        pfd[0].events = POLLIN;
+        pfd[0].fd = event->fd_signal;
+
+        while (pid > 0) {
+                int timeout;
+                int fdcount;
+
+                if (event->timeout_usec > 0) {
+                        unsigned long long age_usec;
+
+                        age_usec = now_usec() - event->birth_usec;
+                        if (age_usec >= event->timeout_usec)
+                                timeout = 1000;
+                        else
+                                timeout = ((event->timeout_usec - age_usec) / 1000) + 1000;
+                } else {
+                        timeout = -1;
+                }
+
+                fdcount = poll(pfd, 1, timeout);
+                if (fdcount < 0) {
+                        if (errno == EINTR)
+                                continue;
+                        err = -errno;
+                        err(udev, "failed to poll: %m\n");
+                        goto out;
+                }
+                if (fdcount == 0) {
+                        err(udev, "timeout: killing '%s' [%u]\n", cmd, pid);
+                        kill(pid, SIGKILL);
+                }
+
+                if (pfd[0].revents & POLLIN) {
+                        struct signalfd_siginfo fdsi;
+                        int status;
+                        ssize_t size;
+
+                        size = read(event->fd_signal, &fdsi, sizeof(struct signalfd_siginfo));
+                        if (size != sizeof(struct signalfd_siginfo))
+                                continue;
+
+                        switch (fdsi.ssi_signo) {
+                        case SIGTERM:
+                                event->sigterm = true;
+                                break;
+                        case SIGCHLD:
+                                if (waitpid(pid, &status, WNOHANG) < 0)
+                                        break;
+                                if (WIFEXITED(status)) {
+                                        info(udev, "'%s' [%u] exit with return code %i\n", cmd, pid, WEXITSTATUS(status));
+                                        if (WEXITSTATUS(status) != 0)
+                                                err = -1;
+                                } else if (WIFSIGNALED(status)) {
+                                        err(udev, "'%s' [%u] terminated by signal %i (%s)\n", cmd, pid, WTERMSIG(status), strsignal(WTERMSIG(status)));
+                                        err = -1;
+                                } else if (WIFSTOPPED(status)) {
+                                        err(udev, "'%s' [%u] stopped\n", cmd, pid);
+                                        err = -1;
+                                } else if (WIFCONTINUED(status)) {
+                                        err(udev, "'%s' [%u] continued\n", cmd, pid);
+                                        err = -1;
+                                } else {
+                                        err(udev, "'%s' [%u] exit with status 0x%04x\n", cmd, pid, status);
+                                        err = -1;
+                                }
+                                pid = 0;
+                                break;
+                        }
+                }
+        }
+out:
+        return err;
+}
+
+int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[])
+{
+        int i = 0;
+        char *pos;
+
+        if (strchr(cmd, ' ') == NULL) {
+                argv[i++] = cmd;
+                goto out;
+        }
+
+        pos = cmd;
+        while (pos != NULL && pos[0] != '\0') {
+                if (pos[0] == '\'') {
+                        /* do not separate quotes */
+                        pos++;
+                        argv[i] = strsep(&pos, "\'");
+                        if (pos != NULL)
+                                while (pos[0] == ' ')
+                                        pos++;
+                } else {
+                        argv[i] = strsep(&pos, " ");
+                        if (pos != NULL)
+                                while (pos[0] == ' ')
+                                        pos++;
+                }
+                dbg(udev, "argv[%i] '%s'\n", i, argv[i]);
+                i++;
+        }
+out:
+        argv[i] = NULL;
+        if (argc)
+                *argc = i;
+        return 0;
+}
+
+int udev_event_spawn(struct udev_event *event,
+                     const char *cmd, char **envp, const sigset_t *sigmask,
+                     char *result, size_t ressize)
+{
+        struct udev *udev = event->udev;
+        int outpipe[2] = {-1, -1};
+        int errpipe[2] = {-1, -1};
+        pid_t pid;
+        char arg[UTIL_PATH_SIZE];
+        char *argv[128];
+        char program[UTIL_PATH_SIZE];
+        int err = 0;
+
+        util_strscpy(arg, sizeof(arg), cmd);
+        udev_build_argv(event->udev, arg, NULL, argv);
+
+        /* pipes from child to parent */
+        if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) {
+                if (pipe2(outpipe, O_NONBLOCK) != 0) {
+                        err = -errno;
+                        err(udev, "pipe failed: %m\n");
+                        goto out;
+                }
+        }
+        if (udev_get_log_priority(udev) >= LOG_INFO) {
+                if (pipe2(errpipe, O_NONBLOCK) != 0) {
+                        err = -errno;
+                        err(udev, "pipe failed: %m\n");
+                        goto out;
+                }
+        }
+
+        /* allow programs in /usr/lib/udev/ to be called without the path */
+        if (argv[0][0] != '/') {
+                util_strscpyl(program, sizeof(program), PKGLIBEXECDIR "/", argv[0], NULL);
+                argv[0] = program;
+        }
+
+        pid = fork();
+        switch(pid) {
+        case 0:
+                /* child closes parent's ends of pipes */
+                if (outpipe[READ_END] >= 0) {
+                        close(outpipe[READ_END]);
+                        outpipe[READ_END] = -1;
+                }
+                if (errpipe[READ_END] >= 0) {
+                        close(errpipe[READ_END]);
+                        errpipe[READ_END] = -1;
+                }
+
+                info(udev, "starting '%s'\n", cmd);
+
+                err = spawn_exec(event, cmd, argv, envp, sigmask,
+                                 outpipe[WRITE_END], errpipe[WRITE_END]);
+
+                _exit(2 );
+        case -1:
+                err(udev, "fork of '%s' failed: %m\n", cmd);
+                err = -1;
+                goto out;
+        default:
+                /* parent closed child's ends of pipes */
+                if (outpipe[WRITE_END] >= 0) {
+                        close(outpipe[WRITE_END]);
+                        outpipe[WRITE_END] = -1;
+                }
+                if (errpipe[WRITE_END] >= 0) {
+                        close(errpipe[WRITE_END]);
+                        errpipe[WRITE_END] = -1;
+                }
+
+                spawn_read(event, cmd,
+                         outpipe[READ_END], errpipe[READ_END],
+                         result, ressize);
+
+                err = spawn_wait(event, cmd, pid);
+        }
+
+out:
+        if (outpipe[READ_END] >= 0)
+                close(outpipe[READ_END]);
+        if (outpipe[WRITE_END] >= 0)
+                close(outpipe[WRITE_END]);
+        if (errpipe[READ_END] >= 0)
+                close(errpipe[READ_END]);
+        if (errpipe[WRITE_END] >= 0)
+                close(errpipe[WRITE_END]);
+        return err;
+}
+
+static void rename_netif_kernel_log(struct ifreq ifr)
+{
+        int klog;
+        FILE *f;
+
+        klog = open("/dev/kmsg", O_WRONLY);
+        if (klog < 0)
+                return;
+
+        f = fdopen(klog, "w");
+        if (f == NULL) {
+                close(klog);
+                return;
+        }
+
+        fprintf(f, "<30>udevd[%u]: renamed network interface %s to %s\n",
+                getpid(), ifr.ifr_name, ifr.ifr_newname);
+        fclose(f);
+}
+
+static int rename_netif(struct udev_event *event)
+{
+        struct udev_device *dev = event->dev;
+        int sk;
+        struct ifreq ifr;
+        int loop;
+        int err;
+
+        info(event->udev, "changing net interface name from '%s' to '%s'\n",
+             udev_device_get_sysname(dev), event->name);
+
+        sk = socket(PF_INET, SOCK_DGRAM, 0);
+        if (sk < 0) {
+                err = -errno;
+                err(event->udev, "error opening socket: %m\n");
+                return err;
+        }
+
+        memset(&ifr, 0x00, sizeof(struct ifreq));
+        util_strscpy(ifr.ifr_name, IFNAMSIZ, udev_device_get_sysname(dev));
+        util_strscpy(ifr.ifr_newname, IFNAMSIZ, event->name);
+        err = ioctl(sk, SIOCSIFNAME, &ifr);
+        if (err == 0) {
+                rename_netif_kernel_log(ifr);
+                goto out;
+        }
+
+        /* keep trying if the destination interface name already exists */
+        err = -errno;
+        if (err != -EEXIST)
+                goto out;
+
+        /* free our own name, another process may wait for us */
+        snprintf(ifr.ifr_newname, IFNAMSIZ, "rename%u", udev_device_get_ifindex(dev));
+        err = ioctl(sk, SIOCSIFNAME, &ifr);
+        if (err < 0) {
+                err = -errno;
+                goto out;
+        }
+
+        /* log temporary name */
+        rename_netif_kernel_log(ifr);
+
+        /* wait a maximum of 90 seconds for our target to become available */
+        util_strscpy(ifr.ifr_name, IFNAMSIZ, ifr.ifr_newname);
+        util_strscpy(ifr.ifr_newname, IFNAMSIZ, event->name);
+        loop = 90 * 20;
+        while (loop--) {
+                const struct timespec duration = { 0, 1000 * 1000 * 1000 / 20 };
+
+                dbg(event->udev, "wait for netif '%s' to become free, loop=%i\n",
+                    event->name, (90 * 20) - loop);
+                nanosleep(&duration, NULL);
+
+                err = ioctl(sk, SIOCSIFNAME, &ifr);
+                if (err == 0) {
+                        rename_netif_kernel_log(ifr);
+                        break;
+                }
+                err = -errno;
+                if (err != -EEXIST)
+                        break;
+        }
+
+out:
+        if (err < 0)
+                err(event->udev, "error changing net interface name %s to %s: %m\n", ifr.ifr_name, ifr.ifr_newname);
+        close(sk);
+        return err;
+}
+
+int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules, const sigset_t *sigmask)
+{
+        struct udev_device *dev = event->dev;
+        int err = 0;
+
+        if (udev_device_get_subsystem(dev) == NULL)
+                return -1;
+
+        if (strcmp(udev_device_get_action(dev), "remove") == 0) {
+                udev_device_read_db(dev, NULL);
+                udev_device_delete_db(dev);
+                udev_device_tag_index(dev, NULL, false);
+
+                if (major(udev_device_get_devnum(dev)) != 0)
+                        udev_watch_end(event->udev, dev);
+
+                udev_rules_apply_to_event(rules, event, sigmask);
+
+                if (major(udev_device_get_devnum(dev)) != 0)
+                        udev_node_remove(dev);
+        } else {
+                event->dev_db = udev_device_new_from_syspath(event->udev, udev_device_get_syspath(dev));
+                if (event->dev_db != NULL) {
+                        udev_device_read_db(event->dev_db, NULL);
+                        udev_device_set_info_loaded(event->dev_db);
+
+                        /* disable watch during event processing */
+                        if (major(udev_device_get_devnum(dev)) != 0)
+                                udev_watch_end(event->udev, event->dev_db);
+                }
+
+                udev_rules_apply_to_event(rules, event, sigmask);
+
+                /* rename a new network interface, if needed */
+                if (udev_device_get_ifindex(dev) > 0 && strcmp(udev_device_get_action(dev), "add") == 0 &&
+                    event->name != NULL && strcmp(event->name, udev_device_get_sysname(dev)) != 0) {
+                        char syspath[UTIL_PATH_SIZE];
+                        char *pos;
+
+                        err = rename_netif(event);
+                        if (err == 0) {
+                                info(event->udev, "renamed netif to '%s'\n", event->name);
+
+                                /* remember old name */
+                                udev_device_add_property(dev, "INTERFACE_OLD", udev_device_get_sysname(dev));
+
+                                /* now change the devpath, because the kernel device name has changed */
+                                util_strscpy(syspath, sizeof(syspath), udev_device_get_syspath(dev));
+                                pos = strrchr(syspath, '/');
+                                if (pos != NULL) {
+                                        pos++;
+                                        util_strscpy(pos, sizeof(syspath) - (pos - syspath), event->name);
+                                        udev_device_set_syspath(event->dev, syspath);
+                                        udev_device_add_property(dev, "INTERFACE", udev_device_get_sysname(dev));
+                                        info(event->udev, "changed devpath to '%s'\n", udev_device_get_devpath(dev));
+                                }
+                        }
+                }
+
+                if (major(udev_device_get_devnum(dev)) > 0) {
+                        /* remove/update possible left-over symlinks from old database entry */
+                        if (event->dev_db != NULL)
+                                udev_node_update_old_links(dev, event->dev_db);
+
+                        if (!event->mode_set) {
+                                if (udev_device_get_devnode_mode(dev) > 0) {
+                                        /* kernel supplied value */
+                                        event->mode = udev_device_get_devnode_mode(dev);
+                                } else if (event->gid > 0) {
+                                        /* default 0660 if a group is assigned */
+                                        event->mode = 0660;
+                                } else {
+                                        /* default 0600 */
+                                        event->mode = 0600;
+                                }
+                        }
+
+                        udev_node_add(dev, event->mode, event->uid, event->gid);
+                }
+
+                /* preserve old, or get new initialization timestamp */
+                if (event->dev_db != NULL && udev_device_get_usec_initialized(event->dev_db) > 0)
+                        udev_device_set_usec_initialized(event->dev, udev_device_get_usec_initialized(event->dev_db));
+                else if (udev_device_get_usec_initialized(event->dev) == 0)
+                        udev_device_set_usec_initialized(event->dev, now_usec());
+
+                /* (re)write database file */
+                udev_device_update_db(dev);
+                udev_device_tag_index(dev, event->dev_db, true);
+                udev_device_set_is_initialized(dev);
+
+                udev_device_unref(event->dev_db);
+                event->dev_db = NULL;
+        }
+out:
+        return err;
+}
+
+int udev_event_execute_run(struct udev_event *event, const sigset_t *sigmask)
+{
+        struct udev_list_entry *list_entry;
+        int err = 0;
+
+        dbg(event->udev, "executing run list\n");
+        udev_list_entry_foreach(list_entry, udev_list_get_entry(&event->run_list)) {
+                const char *cmd = udev_list_entry_get_name(list_entry);
+
+                if (strncmp(cmd, "socket:", strlen("socket:")) == 0) {
+                        struct udev_monitor *monitor;
+
+                        monitor = udev_monitor_new_from_socket(event->udev, &cmd[strlen("socket:")]);
+                        if (monitor == NULL)
+                                continue;
+                        udev_monitor_send_device(monitor, NULL, event->dev);
+                        udev_monitor_unref(monitor);
+                } else {
+                        char program[UTIL_PATH_SIZE];
+                        char **envp;
+
+                        if (event->exec_delay > 0) {
+                                info(event->udev, "delay execution of '%s'\n", program);
+                                sleep(event->exec_delay);
+                        }
+
+                        udev_event_apply_format(event, cmd, program, sizeof(program));
+                        envp = udev_device_get_properties_envp(event->dev);
+                        if (udev_event_spawn(event, program, envp, sigmask, NULL, 0) < 0) {
+                                if (udev_list_entry_get_num(list_entry))
+                                        err = -1;
+                        }
+                }
+        }
+        return err;
+}
diff --git a/src/udev-kernel.socket b/src/udev-kernel.socket
new file mode 100644
index 0000000..23fa9d5
--- /dev/null
+++ b/src/udev-kernel.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=udev Kernel Socket
+DefaultDependencies=no
+ConditionCapability=CAP_MKNOD
+
+[Socket]
+Service=udev.service
+ReceiveBuffer=134217728
+ListenNetlink=kobject-uevent 1
+PassCredentials=yes
diff --git a/src/udev-node.c b/src/udev-node.c
new file mode 100644
index 0000000..7a01a47
--- /dev/null
+++ b/src/udev-node.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2003-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+#define TMP_FILE_EXT                ".udev-tmp"
+
+static int node_symlink(struct udev *udev, const char *node, const char *slink)
+{
+        struct stat stats;
+        char target[UTIL_PATH_SIZE];
+        char *s;
+        size_t l;
+        char slink_tmp[UTIL_PATH_SIZE + sizeof(TMP_FILE_EXT)];
+        int i = 0;
+        int tail = 0;
+        int err = 0;
+
+        /* use relative link */
+        target[0] = '\0';
+        while (node[i] && (node[i] == slink[i])) {
+                if (node[i] == '/')
+                        tail = i+1;
+                i++;
+        }
+        s = target;
+        l = sizeof(target);
+        while (slink[i] != '\0') {
+                if (slink[i] == '/')
+                        l = util_strpcpy(&s, l, "../");
+                i++;
+        }
+        l = util_strscpy(s, l, &node[tail]);
+        if (l == 0) {
+                err = -EINVAL;
+                goto exit;
+        }
+
+        /* preserve link with correct target, do not replace node of other device */
+        if (lstat(slink, &stats) == 0) {
+                if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) {
+                        struct stat stats2;
+
+                        info(udev, "found existing node instead of symlink '%s'\n", slink);
+                        if (lstat(node, &stats2) == 0) {
+                                if ((stats.st_mode & S_IFMT) == (stats2.st_mode & S_IFMT) &&
+                                    stats.st_rdev == stats2.st_rdev && stats.st_ino != stats2.st_ino) {
+                                        info(udev, "replace device node '%s' with symlink to our node '%s'\n",
+                                             slink, node);
+                                } else {
+                                        err(udev, "device node '%s' already exists, "
+                                            "link to '%s' will not overwrite it\n",
+                                            slink, node);
+                                        goto exit;
+                                }
+                        }
+                } else if (S_ISLNK(stats.st_mode)) {
+                        char buf[UTIL_PATH_SIZE];
+                        int len;
+
+                        dbg(udev, "found existing symlink '%s'\n", slink);
+                        len = readlink(slink, buf, sizeof(buf));
+                        if (len > 0 && len < (int)sizeof(buf)) {
+                                buf[len] = '\0';
+                                if (strcmp(target, buf) == 0) {
+                                        info(udev, "preserve already existing symlink '%s' to '%s'\n",
+                                             slink, target);
+                                        udev_selinux_lsetfilecon(udev, slink, S_IFLNK);
+                                        utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW);
+                                        goto exit;
+                                }
+                        }
+                }
+        } else {
+                info(udev, "creating symlink '%s' to '%s'\n", slink, target);
+                do {
+                        err = util_create_path_selinux(udev, slink);
+                        if (err != 0 && err != -ENOENT)
+                                break;
+                        udev_selinux_setfscreatecon(udev, slink, S_IFLNK);
+                        err = symlink(target, slink);
+                        if (err != 0)
+                                err = -errno;
+                        udev_selinux_resetfscreatecon(udev);
+                } while (err == -ENOENT);
+                if (err == 0)
+                        goto exit;
+        }
+
+        info(udev, "atomically replace '%s'\n", slink);
+        util_strscpyl(slink_tmp, sizeof(slink_tmp), slink, TMP_FILE_EXT, NULL);
+        unlink(slink_tmp);
+        do {
+                err = util_create_path_selinux(udev, slink_tmp);
+                if (err != 0 && err != -ENOENT)
+                        break;
+                udev_selinux_setfscreatecon(udev, slink_tmp, S_IFLNK);
+                err = symlink(target, slink_tmp);
+                if (err != 0)
+                        err = -errno;
+                udev_selinux_resetfscreatecon(udev);
+        } while (err == -ENOENT);
+        if (err != 0) {
+                err(udev, "symlink '%s' '%s' failed: %m\n", target, slink_tmp);
+                goto exit;
+        }
+        err = rename(slink_tmp, slink);
+        if (err != 0) {
+                err(udev, "rename '%s' '%s' failed: %m\n", slink_tmp, slink);
+                unlink(slink_tmp);
+        }
+exit:
+        return err;
+}
+
+/* find device node of device with highest priority */
+static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        DIR *dir;
+        int priority = 0;
+        const char *target = NULL;
+
+        if (add) {
+                priority = udev_device_get_devlink_priority(dev);
+                util_strscpy(buf, bufsize, udev_device_get_devnode(dev));
+                target = buf;
+        }
+
+        dir = opendir(stackdir);
+        if (dir == NULL)
+                return target;
+        for (;;) {
+                struct udev_device *dev_db;
+                struct dirent *dent;
+
+                dent = readdir(dir);
+                if (dent == NULL || dent->d_name[0] == '\0')
+                        break;
+                if (dent->d_name[0] == '.')
+                        continue;
+
+                info(udev, "found '%s' claiming '%s'\n", dent->d_name, stackdir);
+
+                /* did we find ourself? */
+                if (strcmp(dent->d_name, udev_device_get_id_filename(dev)) == 0)
+                        continue;
+
+                dev_db = udev_device_new_from_id_filename(udev, dent->d_name);
+                if (dev_db != NULL) {
+                        const char *devnode;
+
+                        devnode = udev_device_get_devnode(dev_db);
+                        if (devnode != NULL) {
+                                dbg(udev, "compare priority of '%s'(%i) > '%s'(%i)\n", target, priority,
+                                    udev_device_get_devnode(dev_db), udev_device_get_devlink_priority(dev_db));
+                                if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) {
+                                        info(udev, "'%s' claims priority %i for '%s'\n",
+                                             udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir);
+                                        priority = udev_device_get_devlink_priority(dev_db);
+                                        util_strscpy(buf, bufsize, devnode);
+                                        target = buf;
+                                }
+                        }
+                        udev_device_unref(dev_db);
+                }
+        }
+        closedir(dir);
+        return target;
+}
+
+/* manage "stack of names" with possibly specified device priorities */
+static void link_update(struct udev_device *dev, const char *slink, bool add)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        char name_enc[UTIL_PATH_SIZE];
+        char filename[UTIL_PATH_SIZE * 2];
+        char dirname[UTIL_PATH_SIZE];
+        const char *target;
+        char buf[UTIL_PATH_SIZE];
+
+        dbg(udev, "update symlink '%s' of '%s'\n", slink, udev_device_get_syspath(dev));
+
+        util_path_encode(&slink[strlen(udev_get_dev_path(udev))+1], name_enc, sizeof(name_enc));
+        util_strscpyl(dirname, sizeof(dirname), udev_get_run_path(udev), "/links/", name_enc, NULL);
+        util_strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL);
+
+        if (!add) {
+                dbg(udev, "removing index: '%s'\n", filename);
+                if (unlink(filename) == 0)
+                        rmdir(dirname);
+        }
+
+        target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf));
+        if (target == NULL) {
+                info(udev, "no reference left, remove '%s'\n", slink);
+                if (unlink(slink) == 0)
+                        util_delete_path(udev, slink);
+        } else {
+                info(udev, "creating link '%s' to '%s'\n", slink, target);
+                node_symlink(udev, target, slink);
+        }
+
+        if (add) {
+                int err;
+
+                dbg(udev, "creating index: '%s'\n", filename);
+                do {
+                        int fd;
+
+                        err = util_create_path(udev, filename);
+                        if (err != 0 && err != -ENOENT)
+                                break;
+                        fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
+                        if (fd >= 0)
+                                close(fd);
+                        else
+                                err = -errno;
+                } while (err == -ENOENT);
+        }
+}
+
+void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        struct udev_list_entry *list_entry;
+
+        /* update possible left-over symlinks */
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) {
+                const char *name = udev_list_entry_get_name(list_entry);
+                struct udev_list_entry *list_entry_current;
+                int found;
+
+                /* check if old link name still belongs to this device */
+                found = 0;
+                udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) {
+                        const char *name_current = udev_list_entry_get_name(list_entry_current);
+
+                        if (strcmp(name, name_current) == 0) {
+                                found = 1;
+                                break;
+                        }
+                }
+                if (found)
+                        continue;
+
+                info(udev, "update old name, '%s' no longer belonging to '%s'\n",
+                     name, udev_device_get_devpath(dev));
+                link_update(dev, name, 0);
+        }
+}
+
+static int node_fixup(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        const char *devnode = udev_device_get_devnode(dev);
+        dev_t devnum = udev_device_get_devnum(dev);
+        struct stat stats;
+        int err = 0;
+
+        if (strcmp(udev_device_get_subsystem(dev), "block") == 0)
+                mode |= S_IFBLK;
+        else
+                mode |= S_IFCHR;
+
+        if (lstat(devnode, &stats) != 0) {
+                err = -errno;
+                info(udev, "can not stat() node '%s' (%m)\n", devnode);
+                goto out;
+        }
+
+        if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) {
+                err = -EEXIST;
+                info(udev, "found node '%s' with non-matching devnum %s, skip handling\n",
+                     udev_device_get_devnode(dev), udev_device_get_id_filename(dev));
+                goto out;
+        }
+
+        if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) {
+                info(udev, "set permissions %s, %#o, uid=%u, gid=%u\n", devnode, mode, uid, gid);
+                chmod(devnode, mode);
+                chown(devnode, uid, gid);
+        } else {
+                info(udev, "preserve permissions %s, %#o, uid=%u, gid=%u\n", devnode, mode, uid, gid);
+        }
+
+        /*
+         * Set initial selinux file context only on add events.
+         * We set the proper context on bootup (triger) or for newly
+         * added devices, but we don't change it later, in case
+         * something else has set a custom context in the meantime.
+         */
+        if (strcmp(udev_device_get_action(dev), "add") == 0)
+                udev_selinux_lsetfilecon(udev, devnode, mode);
+
+        /* always update timestamp when we re-use the node, like on media change events */
+        utimensat(AT_FDCWD, devnode, NULL, 0);
+out:
+        return err;
+}
+
+void udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        char filename[UTIL_PATH_SIZE];
+        struct udev_list_entry *list_entry;
+        int err = 0;
+
+        info(udev, "handling device node '%s', devnum=%s, mode=%#o, uid=%d, gid=%d\n",
+             udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid);
+
+        if (node_fixup(dev, mode, uid, gid) < 0)
+                return;
+
+        /* always add /dev/{block,char}/$major:$minor */
+        snprintf(filename, sizeof(filename), "%s/%s/%u:%u",
+                 udev_get_dev_path(udev),
+                 strcmp(udev_device_get_subsystem(dev), "block") == 0 ? "block" : "char",
+                 major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)));
+        node_symlink(udev, udev_device_get_devnode(dev), filename);
+
+        /* create/update symlinks, add symlinks to name index */
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) {
+                if (udev_list_entry_get_num(list_entry))
+                        /* simple unmanaged link name */
+                        node_symlink(udev, udev_device_get_devnode(dev), udev_list_entry_get_name(list_entry));
+                else
+                        link_update(dev, udev_list_entry_get_name(list_entry), 1);
+        }
+}
+
+void udev_node_remove(struct udev_device *dev)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        struct udev_list_entry *list_entry;
+        const char *devnode;
+        struct stat stats;
+        struct udev_device *dev_check;
+        char filename[UTIL_PATH_SIZE];
+
+        /* remove/update symlinks, remove symlinks from name index */
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
+                link_update(dev, udev_list_entry_get_name(list_entry), 0);
+
+        /* remove /dev/{block,char}/$major:$minor */
+        snprintf(filename, sizeof(filename), "%s/%s/%u:%u",
+                 udev_get_dev_path(udev),
+                 strcmp(udev_device_get_subsystem(dev), "block") == 0 ? "block" : "char",
+                 major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)));
+        unlink(filename);
+}
diff --git a/src/udev-rules.c b/src/udev-rules.c
new file mode 100644
index 0000000..8a85eae
--- /dev/null
+++ b/src/udev-rules.c
@@ -0,0 +1,2767 @@
+/*
+ * Copyright (C) 2003-2010 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Alan Jenkins <alan-jenkins@tuffmail.co.uk>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <time.h>
+
+#include "udev.h"
+
+#define PREALLOC_TOKEN          2048
+#define PREALLOC_STRBUF         32 * 1024
+#define PREALLOC_TRIE           256
+
+struct uid_gid {
+        unsigned int name_off;
+        union {
+                uid_t uid;
+                gid_t gid;
+        };
+};
+
+struct trie_node {
+        /* this node's first child */
+        unsigned int child_idx;
+        /* the next child of our parent node's child list */
+        unsigned int next_child_idx;
+        /* this node's last child (shortcut for append) */
+        unsigned int last_child_idx;
+        unsigned int value_off;
+        unsigned short value_len;
+        unsigned char key;
+};
+
+struct udev_rules {
+        struct udev *udev;
+        int resolve_names;
+
+        /* every key in the rules file becomes a token */
+        struct token *tokens;
+        unsigned int token_cur;
+        unsigned int token_max;
+
+        /* all key strings are copied to a single string buffer */
+        char *buf;
+        size_t buf_cur;
+        size_t buf_max;
+        unsigned int buf_count;
+
+        /* during rule parsing, strings are indexed to find duplicates */
+        struct trie_node *trie_nodes;
+        unsigned int trie_nodes_cur;
+        unsigned int trie_nodes_max;
+
+        /* during rule parsing, uid/gid lookup results are cached */
+        struct uid_gid *uids;
+        unsigned int uids_cur;
+        unsigned int uids_max;
+        struct uid_gid *gids;
+        unsigned int gids_cur;
+        unsigned int gids_max;
+};
+
+/* KEY=="", KEY!="", KEY+="", KEY="", KEY:="" */
+enum operation_type {
+        OP_UNSET,
+
+        OP_MATCH,
+        OP_NOMATCH,
+        OP_MATCH_MAX,
+
+        OP_ADD,
+        OP_ASSIGN,
+        OP_ASSIGN_FINAL,
+};
+
+enum string_glob_type {
+        GL_UNSET,
+        GL_PLAIN,                       /* no special chars */
+        GL_GLOB,                        /* shell globs ?,*,[] */
+        GL_SPLIT,                       /* multi-value A|B */
+        GL_SPLIT_GLOB,                  /* multi-value with glob A*|B* */
+        GL_SOMETHING,                   /* commonly used "?*" */
+};
+
+enum string_subst_type {
+        SB_UNSET,
+        SB_NONE,
+        SB_FORMAT,
+        SB_SUBSYS,
+};
+
+/* tokens of a rule are sorted/handled in this order */
+enum token_type {
+        TK_UNSET,
+        TK_RULE,
+
+        TK_M_ACTION,                    /* val */
+        TK_M_DEVPATH,                   /* val */
+        TK_M_KERNEL,                    /* val */
+        TK_M_DEVLINK,                   /* val */
+        TK_M_NAME,                      /* val */
+        TK_M_ENV,                       /* val, attr */
+        TK_M_TAG,                       /* val */
+        TK_M_SUBSYSTEM,                 /* val */
+        TK_M_DRIVER,                    /* val */
+        TK_M_WAITFOR,                   /* val */
+        TK_M_ATTR,                      /* val, attr */
+
+        TK_M_PARENTS_MIN,
+        TK_M_KERNELS,                   /* val */
+        TK_M_SUBSYSTEMS,                /* val */
+        TK_M_DRIVERS,                   /* val */
+        TK_M_ATTRS,                     /* val, attr */
+        TK_M_TAGS,                      /* val */
+        TK_M_PARENTS_MAX,
+
+        TK_M_TEST,                      /* val, mode_t */
+        TK_M_EVENT_TIMEOUT,             /* int */
+        TK_M_PROGRAM,                   /* val */
+        TK_M_IMPORT_FILE,               /* val */
+        TK_M_IMPORT_PROG,               /* val */
+        TK_M_IMPORT_BUILTIN,            /* val */
+        TK_M_IMPORT_DB,                 /* val */
+        TK_M_IMPORT_CMDLINE,            /* val */
+        TK_M_IMPORT_PARENT,             /* val */
+        TK_M_RESULT,                    /* val */
+        TK_M_MAX,
+
+        TK_A_STRING_ESCAPE_NONE,
+        TK_A_STRING_ESCAPE_REPLACE,
+        TK_A_DB_PERSIST,
+        TK_A_INOTIFY_WATCH,             /* int */
+        TK_A_DEVLINK_PRIO,              /* int */
+        TK_A_OWNER,                     /* val */
+        TK_A_GROUP,                     /* val */
+        TK_A_MODE,                      /* val */
+        TK_A_OWNER_ID,                  /* uid_t */
+        TK_A_GROUP_ID,                  /* gid_t */
+        TK_A_MODE_ID,                   /* mode_t */
+        TK_A_STATIC_NODE,               /* val */
+        TK_A_ENV,                       /* val, attr */
+        TK_A_TAG,                       /* val */
+        TK_A_NAME,                      /* val */
+        TK_A_DEVLINK,                   /* val */
+        TK_A_ATTR,                      /* val, attr */
+        TK_A_RUN,                       /* val, bool */
+        TK_A_GOTO,                      /* size_t */
+
+        TK_END,
+};
+
+/* we try to pack stuff in a way that we take only 12 bytes per token */
+struct token {
+        union {
+                unsigned char type;                /* same in rule and key */
+                struct {
+                        enum token_type type:8;
+                        bool can_set_name:1;
+                        bool has_static_node:1;
+                        unsigned int unused:6;
+                        unsigned short token_count;
+                        unsigned int label_off;
+                        unsigned short filename_off;
+                        unsigned short filename_line;
+                } rule;
+                struct {
+                        enum token_type type:8;
+                        enum operation_type op:8;
+                        enum string_glob_type glob:8;
+                        enum string_subst_type subst:4;
+                        enum string_subst_type attrsubst:4;
+                        unsigned int value_off;
+                        union {
+                                unsigned int attr_off;
+                                int devlink_unique;
+                                unsigned int rule_goto;
+                                mode_t  mode;
+                                uid_t uid;
+                                gid_t gid;
+                                int devlink_prio;
+                                int event_timeout;
+                                int watch;
+                                enum udev_builtin_cmd builtin_cmd;
+                        };
+                } key;
+        };
+};
+
+#define MAX_TK                64
+struct rule_tmp {
+        struct udev_rules *rules;
+        struct token rule;
+        struct token token[MAX_TK];
+        unsigned int token_cur;
+};
+
+#ifdef ENABLE_DEBUG
+static const char *operation_str(enum operation_type type)
+{
+        static const char *operation_strs[] = {
+                [OP_UNSET] =            "UNSET",
+                [OP_MATCH] =            "match",
+                [OP_NOMATCH] =          "nomatch",
+                [OP_MATCH_MAX] =        "MATCH_MAX",
+
+                [OP_ADD] =              "add",
+                [OP_ASSIGN] =           "assign",
+                [OP_ASSIGN_FINAL] =     "assign-final",
+}        ;
+
+        return operation_strs[type];
+}
+
+static const char *string_glob_str(enum string_glob_type type)
+{
+        static const char *string_glob_strs[] = {
+                [GL_UNSET] =            "UNSET",
+                [GL_PLAIN] =            "plain",
+                [GL_GLOB] =             "glob",
+                [GL_SPLIT] =            "split",
+                [GL_SPLIT_GLOB] =       "split-glob",
+                [GL_SOMETHING] =        "split-glob",
+        };
+
+        return string_glob_strs[type];
+}
+
+static const char *token_str(enum token_type type)
+{
+        static const char *token_strs[] = {
+                [TK_UNSET] =                    "UNSET",
+                [TK_RULE] =                     "RULE",
+
+                [TK_M_ACTION] =                 "M ACTION",
+                [TK_M_DEVPATH] =                "M DEVPATH",
+                [TK_M_KERNEL] =                 "M KERNEL",
+                [TK_M_DEVLINK] =                "M DEVLINK",
+                [TK_M_NAME] =                   "M NAME",
+                [TK_M_ENV] =                    "M ENV",
+                [TK_M_TAG] =                    "M TAG",
+                [TK_M_SUBSYSTEM] =              "M SUBSYSTEM",
+                [TK_M_DRIVER] =                 "M DRIVER",
+                [TK_M_WAITFOR] =                "M WAITFOR",
+                [TK_M_ATTR] =                   "M ATTR",
+
+                [TK_M_PARENTS_MIN] =            "M PARENTS_MIN",
+                [TK_M_KERNELS] =                "M KERNELS",
+                [TK_M_SUBSYSTEMS] =             "M SUBSYSTEMS",
+                [TK_M_DRIVERS] =                "M DRIVERS",
+                [TK_M_ATTRS] =                  "M ATTRS",
+                [TK_M_TAGS] =                   "M TAGS",
+                [TK_M_PARENTS_MAX] =            "M PARENTS_MAX",
+
+                [TK_M_TEST] =                   "M TEST",
+                [TK_M_EVENT_TIMEOUT] =          "M EVENT_TIMEOUT",
+                [TK_M_PROGRAM] =                "M PROGRAM",
+                [TK_M_IMPORT_FILE] =            "M IMPORT_FILE",
+                [TK_M_IMPORT_PROG] =            "M IMPORT_PROG",
+                [TK_M_IMPORT_BUILTIN] =         "M IMPORT_BUILTIN",
+                [TK_M_IMPORT_DB] =              "M IMPORT_DB",
+                [TK_M_IMPORT_CMDLINE] =         "M IMPORT_CMDLINE",
+                [TK_M_IMPORT_PARENT] =          "M IMPORT_PARENT",
+                [TK_M_RESULT] =                 "M RESULT",
+                [TK_M_MAX] =                    "M MAX",
+
+                [TK_A_STRING_ESCAPE_NONE] =     "A STRING_ESCAPE_NONE",
+                [TK_A_STRING_ESCAPE_REPLACE] =  "A STRING_ESCAPE_REPLACE",
+                [TK_A_DB_PERSIST] =             "A DB_PERSIST",
+                [TK_A_INOTIFY_WATCH] =          "A INOTIFY_WATCH",
+                [TK_A_DEVLINK_PRIO] =           "A DEVLINK_PRIO",
+                [TK_A_OWNER] =                  "A OWNER",
+                [TK_A_GROUP] =                  "A GROUP",
+                [TK_A_MODE] =                   "A MODE",
+                [TK_A_OWNER_ID] =               "A OWNER_ID",
+                [TK_A_GROUP_ID] =               "A GROUP_ID",
+                [TK_A_STATIC_NODE] =            "A STATIC_NODE",
+                [TK_A_MODE_ID] =                "A MODE_ID",
+                [TK_A_ENV] =                    "A ENV",
+                [TK_A_TAG] =                    "A ENV",
+                [TK_A_NAME] =                   "A NAME",
+                [TK_A_DEVLINK] =                "A DEVLINK",
+                [TK_A_ATTR] =                   "A ATTR",
+                [TK_A_RUN] =                    "A RUN",
+                [TK_A_GOTO] =                   "A GOTO",
+
+                [TK_END] =                      "END",
+        };
+
+        return token_strs[type];
+}
+
+static void dump_token(struct udev_rules *rules, struct token *token)
+{
+        enum token_type type = token->type;
+        enum operation_type op = token->key.op;
+        enum string_glob_type glob = token->key.glob;
+        const char *value = &rules->buf[token->key.value_off];
+        const char *attr = &rules->buf[token->key.attr_off];
+
+        switch (type) {
+        case TK_RULE:
+                {
+                        const char *tks_ptr = (char *)rules->tokens;
+                        const char *tk_ptr = (char *)token;
+                        unsigned int idx = (tk_ptr - tks_ptr) / sizeof(struct token);
+
+                        dbg(rules->udev, "* RULE %s:%u, token: %u, count: %u, label: '%s'\n",
+                            &rules->buf[token->rule.filename_off], token->rule.filename_line,
+                            idx, token->rule.token_count,
+                            &rules->buf[token->rule.label_off]);
+                        break;
+                }
+        case TK_M_ACTION:
+        case TK_M_DEVPATH:
+        case TK_M_KERNEL:
+        case TK_M_SUBSYSTEM:
+        case TK_M_DRIVER:
+        case TK_M_WAITFOR:
+        case TK_M_DEVLINK:
+        case TK_M_NAME:
+        case TK_M_KERNELS:
+        case TK_M_SUBSYSTEMS:
+        case TK_M_DRIVERS:
+        case TK_M_TAGS:
+        case TK_M_PROGRAM:
+        case TK_M_IMPORT_FILE:
+        case TK_M_IMPORT_PROG:
+        case TK_M_IMPORT_DB:
+        case TK_M_IMPORT_CMDLINE:
+        case TK_M_IMPORT_PARENT:
+        case TK_M_RESULT:
+        case TK_A_NAME:
+        case TK_A_DEVLINK:
+        case TK_A_OWNER:
+        case TK_A_GROUP:
+        case TK_A_MODE:
+        case TK_A_RUN:
+                dbg(rules->udev, "%s %s '%s'(%s)\n",
+                    token_str(type), operation_str(op), value, string_glob_str(glob));
+                break;
+        case TK_M_IMPORT_BUILTIN:
+                dbg(rules->udev, "%s %i '%s'\n", token_str(type), token->key.builtin_cmd, value);
+                break;
+        case TK_M_ATTR:
+        case TK_M_ATTRS:
+        case TK_M_ENV:
+        case TK_A_ATTR:
+        case TK_A_ENV:
+                dbg(rules->udev, "%s %s '%s' '%s'(%s)\n",
+                    token_str(type), operation_str(op), attr, value, string_glob_str(glob));
+                break;
+        case TK_M_TAG:
+        case TK_A_TAG:
+                dbg(rules->udev, "%s %s '%s'\n", token_str(type), operation_str(op), value);
+                break;
+        case TK_A_STRING_ESCAPE_NONE:
+        case TK_A_STRING_ESCAPE_REPLACE:
+        case TK_A_DB_PERSIST:
+                dbg(rules->udev, "%s\n", token_str(type));
+                break;
+        case TK_M_TEST:
+                dbg(rules->udev, "%s %s '%s'(%s) %#o\n",
+                    token_str(type), operation_str(op), value, string_glob_str(glob), token->key.mode);
+                break;
+        case TK_A_INOTIFY_WATCH:
+                dbg(rules->udev, "%s %u\n", token_str(type), token->key.watch);
+                break;
+        case TK_A_DEVLINK_PRIO:
+                dbg(rules->udev, "%s %u\n", token_str(type), token->key.devlink_prio);
+                break;
+        case TK_A_OWNER_ID:
+                dbg(rules->udev, "%s %s %u\n", token_str(type), operation_str(op), token->key.uid);
+                break;
+        case TK_A_GROUP_ID:
+                dbg(rules->udev, "%s %s %u\n", token_str(type), operation_str(op), token->key.gid);
+                break;
+        case TK_A_MODE_ID:
+                dbg(rules->udev, "%s %s %#o\n", token_str(type), operation_str(op), token->key.mode);
+                break;
+        case TK_A_STATIC_NODE:
+                dbg(rules->udev, "%s '%s'\n", token_str(type), value);
+                break;
+        case TK_M_EVENT_TIMEOUT:
+                dbg(rules->udev, "%s %u\n", token_str(type), token->key.event_timeout);
+                break;
+        case TK_A_GOTO:
+                dbg(rules->udev, "%s '%s' %u\n", token_str(type), value, token->key.rule_goto);
+                break;
+        case TK_END:
+                dbg(rules->udev, "* %s\n", token_str(type));
+                break;
+        case TK_M_PARENTS_MIN:
+        case TK_M_PARENTS_MAX:
+        case TK_M_MAX:
+        case TK_UNSET:
+                dbg(rules->udev, "unknown type %u\n", type);
+                break;
+        }
+}
+
+static void dump_rules(struct udev_rules *rules)
+{
+        unsigned int i;
+
+        dbg(rules->udev, "dumping %u (%zu bytes) tokens, %u (%zu bytes) strings\n",
+            rules->token_cur,
+            rules->token_cur * sizeof(struct token),
+            rules->buf_count,
+            rules->buf_cur);
+        for(i = 0; i < rules->token_cur; i++)
+                dump_token(rules, &rules->tokens[i]);
+}
+#else
+static inline const char *operation_str(enum operation_type type) { return NULL; }
+static inline const char *token_str(enum token_type type) { return NULL; }
+static inline void dump_token(struct udev_rules *rules, struct token *token) {}
+static inline void dump_rules(struct udev_rules *rules) {}
+#endif /* ENABLE_DEBUG */
+
+static int add_new_string(struct udev_rules *rules, const char *str, size_t bytes)
+{
+        int off;
+
+        /* grow buffer if needed */
+        if (rules->buf_cur + bytes+1 >= rules->buf_max) {
+                char *buf;
+                unsigned int add;
+
+                /* double the buffer size */
+                add = rules->buf_max;
+                if (add < bytes * 8)
+                        add = bytes * 8;
+
+                buf = realloc(rules->buf, rules->buf_max + add);
+                if (buf == NULL)
+                        return -1;
+                dbg(rules->udev, "extend buffer from %zu to %zu\n", rules->buf_max, rules->buf_max + add);
+                rules->buf = buf;
+                rules->buf_max += add;
+        }
+        off = rules->buf_cur;
+        memcpy(&rules->buf[rules->buf_cur], str, bytes);
+        rules->buf_cur += bytes;
+        rules->buf_count++;
+        return off;
+}
+
+static int add_string(struct udev_rules *rules, const char *str)
+{
+        unsigned int node_idx;
+        struct trie_node *new_node;
+        unsigned int new_node_idx;
+        unsigned char key;
+        unsigned short len;
+        unsigned int depth;
+        unsigned int off;
+        struct trie_node *parent;
+
+        /* walk trie, start from last character of str to find matching tails */
+        len = strlen(str);
+        key = str[len-1];
+        node_idx = 0;
+        for (depth = 0; depth <= len; depth++) {
+                struct trie_node *node;
+                unsigned int child_idx;
+
+                node = &rules->trie_nodes[node_idx];
+                off = node->value_off + node->value_len - len;
+
+                /* match against current node */
+                if (depth == len || (node->value_len >= len && memcmp(&rules->buf[off], str, len) == 0))
+                        return off;
+
+                /* lookup child node */
+                key = str[len - 1 - depth];
+                child_idx = node->child_idx;
+                while (child_idx > 0) {
+                        struct trie_node *child;
+
+                        child = &rules->trie_nodes[child_idx];
+                        if (child->key == key)
+                                break;
+                        child_idx = child->next_child_idx;
+                }
+                if (child_idx == 0)
+                        break;
+                node_idx = child_idx;
+        }
+
+        /* string not found, add it */
+        off = add_new_string(rules, str, len + 1);
+
+        /* grow trie nodes if needed */
+        if (rules->trie_nodes_cur >= rules->trie_nodes_max) {
+                struct trie_node *nodes;
+                unsigned int add;
+
+                /* double the buffer size */
+                add = rules->trie_nodes_max;
+                if (add < 8)
+                        add = 8;
+
+                nodes = realloc(rules->trie_nodes, (rules->trie_nodes_max + add) * sizeof(struct trie_node));
+                if (nodes == NULL)
+                        return -1;
+                dbg(rules->udev, "extend trie nodes from %u to %u\n",
+                    rules->trie_nodes_max, rules->trie_nodes_max + add);
+                rules->trie_nodes = nodes;
+                rules->trie_nodes_max += add;
+        }
+
+        /* get a new node */
+        new_node_idx = rules->trie_nodes_cur;
+        rules->trie_nodes_cur++;
+        new_node = &rules->trie_nodes[new_node_idx];
+        memset(new_node, 0x00, sizeof(struct trie_node));
+        new_node->value_off = off;
+        new_node->value_len = len;
+        new_node->key = key;
+
+        /* join the parent's child list */
+        parent = &rules->trie_nodes[node_idx];
+        if (parent->child_idx == 0) {
+                parent->child_idx = new_node_idx;
+        } else {
+                struct trie_node *last_child;
+
+                last_child = &rules->trie_nodes[parent->last_child_idx];
+                last_child->next_child_idx = new_node_idx;
+        }
+        parent->last_child_idx = new_node_idx;
+        return off;
+}
+
+static int add_token(struct udev_rules *rules, struct token *token)
+{
+        /* grow buffer if needed */
+        if (rules->token_cur+1 >= rules->token_max) {
+                struct token *tokens;
+                unsigned int add;
+
+                /* double the buffer size */
+                add = rules->token_max;
+                if (add < 8)
+                        add = 8;
+
+                tokens = realloc(rules->tokens, (rules->token_max + add ) * sizeof(struct token));
+                if (tokens == NULL)
+                        return -1;
+                dbg(rules->udev, "extend tokens from %u to %u\n", rules->token_max, rules->token_max + add);
+                rules->tokens = tokens;
+                rules->token_max += add;
+        }
+        memcpy(&rules->tokens[rules->token_cur], token, sizeof(struct token));
+        rules->token_cur++;
+        return 0;
+}
+
+static uid_t add_uid(struct udev_rules *rules, const char *owner)
+{
+        unsigned int i;
+        uid_t uid;
+        unsigned int off;
+
+        /* lookup, if we know it already */
+        for (i = 0; i < rules->uids_cur; i++) {
+                off = rules->uids[i].name_off;
+                if (strcmp(&rules->buf[off], owner) == 0) {
+                        uid = rules->uids[i].uid;
+                        dbg(rules->udev, "return existing %u for '%s'\n", uid, owner);
+                        return uid;
+                }
+        }
+        uid = util_lookup_user(rules->udev, owner);
+
+        /* grow buffer if needed */
+        if (rules->uids_cur+1 >= rules->uids_max) {
+                struct uid_gid *uids;
+                unsigned int add;
+
+                /* double the buffer size */
+                add = rules->uids_max;
+                if (add < 1)
+                        add = 8;
+
+                uids = realloc(rules->uids, (rules->uids_max + add ) * sizeof(struct uid_gid));
+                if (uids == NULL)
+                        return uid;
+                dbg(rules->udev, "extend uids from %u to %u\n", rules->uids_max, rules->uids_max + add);
+                rules->uids = uids;
+                rules->uids_max += add;
+        }
+        rules->uids[rules->uids_cur].uid = uid;
+        off = add_string(rules, owner);
+        if (off <= 0)
+                return uid;
+        rules->uids[rules->uids_cur].name_off = off;
+        rules->uids_cur++;
+        return uid;
+}
+
+static gid_t add_gid(struct udev_rules *rules, const char *group)
+{
+        unsigned int i;
+        gid_t gid;
+        unsigned int off;
+
+        /* lookup, if we know it already */
+        for (i = 0; i < rules->gids_cur; i++) {
+                off = rules->gids[i].name_off;
+                if (strcmp(&rules->buf[off], group) == 0) {
+                        gid = rules->gids[i].gid;
+                        dbg(rules->udev, "return existing %u for '%s'\n", gid, group);
+                        return gid;
+                }
+        }
+        gid = util_lookup_group(rules->udev, group);
+
+        /* grow buffer if needed */
+        if (rules->gids_cur+1 >= rules->gids_max) {
+                struct uid_gid *gids;
+                unsigned int add;
+
+                /* double the buffer size */
+                add = rules->gids_max;
+                if (add < 1)
+                        add = 8;
+
+                gids = realloc(rules->gids, (rules->gids_max + add ) * sizeof(struct uid_gid));
+                if (gids == NULL)
+                        return gid;
+                dbg(rules->udev, "extend gids from %u to %u\n", rules->gids_max, rules->gids_max + add);
+                rules->gids = gids;
+                rules->gids_max += add;
+        }
+        rules->gids[rules->gids_cur].gid = gid;
+        off = add_string(rules, group);
+        if (off <= 0)
+                return gid;
+        rules->gids[rules->gids_cur].name_off = off;
+        rules->gids_cur++;
+        return gid;
+}
+
+static int import_property_from_string(struct udev_device *dev, char *line)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        char *key;
+        char *val;
+        size_t len;
+
+        /* find key */
+        key = line;
+        while (isspace(key[0]))
+                key++;
+
+        /* comment or empty line */
+        if (key[0] == '#' || key[0] == '\0')
+                return -1;
+
+        /* split key/value */
+        val = strchr(key, '=');
+        if (val == NULL)
+                return -1;
+        val[0] = '\0';
+        val++;
+
+        /* find value */
+        while (isspace(val[0]))
+                val++;
+
+        /* terminate key */
+        len = strlen(key);
+        if (len == 0)
+                return -1;
+        while (isspace(key[len-1]))
+                len--;
+        key[len] = '\0';
+
+        /* terminate value */
+        len = strlen(val);
+        if (len == 0)
+                return -1;
+        while (isspace(val[len-1]))
+                len--;
+        val[len] = '\0';
+
+        if (len == 0)
+                return -1;
+
+        /* unquote */
+        if (val[0] == '"' || val[0] == '\'') {
+                if (val[len-1] != val[0]) {
+                        info(udev, "inconsistent quoting: '%s', skip\n", line);
+                        return -1;
+                }
+                val[len-1] = '\0';
+                val++;
+        }
+
+        dbg(udev, "adding '%s'='%s'\n", key, val);
+
+        /* handle device, renamed by external tool, returning new path */
+        if (strcmp(key, "DEVPATH") == 0) {
+                char syspath[UTIL_PATH_SIZE];
+
+                info(udev, "updating devpath from '%s' to '%s'\n",
+                     udev_device_get_devpath(dev), val);
+                util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), val, NULL);
+                udev_device_set_syspath(dev, syspath);
+        } else {
+                struct udev_list_entry *entry;
+
+                entry = udev_device_add_property(dev, key, val);
+                /* store in db, skip private keys */
+                if (key[0] != '.')
+                        udev_list_entry_set_num(entry, true);
+        }
+        return 0;
+}
+
+static int import_file_into_properties(struct udev_device *dev, const char *filename)
+{
+        FILE *f;
+        char line[UTIL_LINE_SIZE];
+
+        f = fopen(filename, "r");
+        if (f == NULL)
+                return -1;
+        while (fgets(line, sizeof(line), f) != NULL)
+                import_property_from_string(dev, line);
+        fclose(f);
+        return 0;
+}
+
+static int import_program_into_properties(struct udev_event *event, const char *program, const sigset_t *sigmask)
+{
+        struct udev_device *dev = event->dev;
+        char **envp;
+        char result[UTIL_LINE_SIZE];
+        char *line;
+        int err;
+
+        envp = udev_device_get_properties_envp(dev);
+        err = udev_event_spawn(event, program, envp, sigmask, result, sizeof(result));
+        if (err < 0)
+                return err;
+
+        line = result;
+        while (line != NULL) {
+                char *pos;
+
+                pos = strchr(line, '\n');
+                if (pos != NULL) {
+                        pos[0] = '\0';
+                        pos = &pos[1];
+                }
+                import_property_from_string(dev, line);
+                line = pos;
+        }
+        return 0;
+}
+
+static int import_parent_into_properties(struct udev_device *dev, const char *filter)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        struct udev_device *dev_parent;
+        struct udev_list_entry *list_entry;
+
+        dev_parent = udev_device_get_parent(dev);
+        if (dev_parent == NULL)
+                return -1;
+
+        dbg(udev, "found parent '%s', get the node name\n", udev_device_get_syspath(dev_parent));
+        udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(dev_parent)) {
+                const char *key = udev_list_entry_get_name(list_entry);
+                const char *val = udev_list_entry_get_value(list_entry);
+
+                if (fnmatch(filter, key, 0) == 0) {
+                        struct udev_list_entry *entry;
+
+                        dbg(udev, "import key '%s=%s'\n", key, val);
+                        entry = udev_device_add_property(dev, key, val);
+                        /* store in db, skip private keys */
+                        if (key[0] != '.')
+                                udev_list_entry_set_num(entry, true);
+                }
+        }
+        return 0;
+}
+
+#define WAIT_LOOP_PER_SECOND                50
+static int wait_for_file(struct udev_device *dev, const char *file, int timeout)
+{
+        struct udev *udev = udev_device_get_udev(dev);
+        char filepath[UTIL_PATH_SIZE];
+        char devicepath[UTIL_PATH_SIZE];
+        struct stat stats;
+        int loop = timeout * WAIT_LOOP_PER_SECOND;
+
+        /* a relative path is a device attribute */
+        devicepath[0] = '\0';
+        if (file[0] != '/') {
+                util_strscpyl(devicepath, sizeof(devicepath),
+                              udev_get_sys_path(udev), udev_device_get_devpath(dev), NULL);
+                util_strscpyl(filepath, sizeof(filepath), devicepath, "/", file, NULL);
+                file = filepath;
+        }
+
+        dbg(udev, "will wait %i sec for '%s'\n", timeout, file);
+        while (--loop) {
+                const struct timespec duration = { 0, 1000 * 1000 * 1000 / WAIT_LOOP_PER_SECOND };
+
+                /* lookup file */
+                if (stat(file, &stats) == 0) {
+                        info(udev, "file '%s' appeared after %i loops\n", file, (timeout * WAIT_LOOP_PER_SECOND) - loop-1);
+                        return 0;
+                }
+                /* make sure, the device did not disappear in the meantime */
+                if (devicepath[0] != '\0' && stat(devicepath, &stats) != 0) {
+                        info(udev, "device disappeared while waiting for '%s'\n", file);
+                        return -2;
+                }
+                info(udev, "wait for '%s' for %i mseconds\n", file, 1000 / WAIT_LOOP_PER_SECOND);
+                nanosleep(&duration, NULL);
+        }
+        info(udev, "waiting for '%s' failed\n", file);
+        return -1;
+}
+
+static int attr_subst_subdir(char *attr, size_t len)
+{
+        bool found = false;
+
+        if (strstr(attr, "/*/")) {
+                char *pos;
+                char dirname[UTIL_PATH_SIZE];
+                const char *tail;
+                DIR *dir;
+
+                util_strscpy(dirname, sizeof(dirname), attr);
+                pos = strstr(dirname, "/*/");
+                if (pos == NULL)
+                        return -1;
+                pos[0] = '\0';
+                tail = &pos[2];
+                dir = opendir(dirname);
+                if (dir != NULL) {
+                        struct dirent *dent;
+
+                        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                                struct stat stats;
+
+                                if (dent->d_name[0] == '.')
+                                        continue;
+                                util_strscpyl(attr, len, dirname, "/", dent->d_name, tail, NULL);
+                                if (stat(attr, &stats) == 0) {
+                                        found = true;
+                                        break;
+                                }
+                        }
+                        closedir(dir);
+                }
+        }
+
+        return found;
+}
+
+static int get_key(struct udev *udev, char **line, char **key, enum operation_type *op, char **value)
+{
+        char *linepos;
+        char *temp;
+
+        linepos = *line;
+        if (linepos == NULL || linepos[0] == '\0')
+                return -1;
+
+        /* skip whitespace */
+        while (isspace(linepos[0]) || linepos[0] == ',')
+                linepos++;
+
+        /* get the key */
+        if (linepos[0] == '\0')
+                return -1;
+        *key = linepos;
+
+        for (;;) {
+                linepos++;
+                if (linepos[0] == '\0')
+                        return -1;
+                if (isspace(linepos[0]))
+                        break;
+                if (linepos[0] == '=')
+                        break;
+                if ((linepos[0] == '+') || (linepos[0] == '!') || (linepos[0] == ':'))
+                        if (linepos[1] == '=')
+                                break;
+        }
+
+        /* remember end of key */
+        temp = linepos;
+
+        /* skip whitespace after key */
+        while (isspace(linepos[0]))
+                linepos++;
+        if (linepos[0] == '\0')
+                return -1;
+
+        /* get operation type */
+        if (linepos[0] == '=' && linepos[1] == '=') {
+                *op = OP_MATCH;
+                linepos += 2;
+        } else if (linepos[0] == '!' && linepos[1] == '=') {
+                *op = OP_NOMATCH;
+                linepos += 2;
+        } else if (linepos[0] == '+' && linepos[1] == '=') {
+                *op = OP_ADD;
+                linepos += 2;
+        } else if (linepos[0] == '=') {
+                *op = OP_ASSIGN;
+                linepos++;
+        } else if (linepos[0] == ':' && linepos[1] == '=') {
+                *op = OP_ASSIGN_FINAL;
+                linepos += 2;
+        } else
+                return -1;
+
+        /* terminate key */
+        temp[0] = '\0';
+
+        /* skip whitespace after operator */
+        while (isspace(linepos[0]))
+                linepos++;
+        if (linepos[0] == '\0')
+                return -1;
+
+        /* get the value */
+        if (linepos[0] == '"')
+                linepos++;
+        else
+                return -1;
+        *value = linepos;
+
+        /* terminate */
+        temp = strchr(linepos, '"');
+        if (!temp)
+                return -1;
+        temp[0] = '\0';
+        temp++;
+        dbg(udev, "%s '%s'-'%s'\n", operation_str(*op), *key, *value);
+
+        /* move line to next key */
+        *line = temp;
+        return 0;
+}
+
+/* extract possible KEY{attr} */
+static char *get_key_attribute(struct udev *udev, char *str)
+{
+        char *pos;
+        char *attr;
+
+        attr = strchr(str, '{');
+        if (attr != NULL) {
+                attr++;
+                pos = strchr(attr, '}');
+                if (pos == NULL) {
+                        err(udev, "missing closing brace for format\n");
+                        return NULL;
+                }
+                pos[0] = '\0';
+                dbg(udev, "attribute='%s'\n", attr);
+                return attr;
+        }
+        return NULL;
+}
+
+static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type,
+                        enum operation_type op,
+                        const char *value, const void *data)
+{
+        struct token *token = &rule_tmp->token[rule_tmp->token_cur];
+        const char *attr = NULL;
+
+        memset(token, 0x00, sizeof(struct token));
+
+        switch (type) {
+        case TK_M_ACTION:
+        case TK_M_DEVPATH:
+        case TK_M_KERNEL:
+        case TK_M_SUBSYSTEM:
+        case TK_M_DRIVER:
+        case TK_M_WAITFOR:
+        case TK_M_DEVLINK:
+        case TK_M_NAME:
+        case TK_M_KERNELS:
+        case TK_M_SUBSYSTEMS:
+        case TK_M_DRIVERS:
+        case TK_M_TAGS:
+        case TK_M_PROGRAM:
+        case TK_M_IMPORT_FILE:
+        case TK_M_IMPORT_PROG:
+        case TK_M_IMPORT_DB:
+        case TK_M_IMPORT_CMDLINE:
+        case TK_M_IMPORT_PARENT:
+        case TK_M_RESULT:
+        case TK_A_OWNER:
+        case TK_A_GROUP:
+        case TK_A_MODE:
+        case TK_A_NAME:
+        case TK_A_GOTO:
+        case TK_M_TAG:
+        case TK_A_TAG:
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                break;
+        case TK_M_IMPORT_BUILTIN:
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                token->key.builtin_cmd = *(enum udev_builtin_cmd *)data;
+                break;
+        case TK_M_ENV:
+        case TK_M_ATTR:
+        case TK_M_ATTRS:
+        case TK_A_ATTR:
+        case TK_A_ENV:
+                attr = data;
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                token->key.attr_off = add_string(rule_tmp->rules, attr);
+                break;
+        case TK_A_DEVLINK:
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                token->key.devlink_unique = *(int *)data;
+                break;
+        case TK_M_TEST:
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                if (data != NULL)
+                        token->key.mode = *(mode_t *)data;
+                break;
+        case TK_A_STRING_ESCAPE_NONE:
+        case TK_A_STRING_ESCAPE_REPLACE:
+        case TK_A_DB_PERSIST:
+                break;
+        case TK_A_RUN:
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                break;
+        case TK_A_INOTIFY_WATCH:
+        case TK_A_DEVLINK_PRIO:
+                token->key.devlink_prio = *(int *)data;
+                break;
+        case TK_A_OWNER_ID:
+                token->key.uid = *(uid_t *)data;
+                break;
+        case TK_A_GROUP_ID:
+                token->key.gid = *(gid_t *)data;
+                break;
+        case TK_A_MODE_ID:
+                token->key.mode = *(mode_t *)data;
+                break;
+        case TK_A_STATIC_NODE:
+                token->key.value_off = add_string(rule_tmp->rules, value);
+                break;
+        case TK_M_EVENT_TIMEOUT:
+                token->key.event_timeout = *(int *)data;
+                break;
+        case TK_RULE:
+        case TK_M_PARENTS_MIN:
+        case TK_M_PARENTS_MAX:
+        case TK_M_MAX:
+        case TK_END:
+        case TK_UNSET:
+                err(rule_tmp->rules->udev, "wrong type %u\n", type);
+                return -1;
+        }
+
+        if (value != NULL && type < TK_M_MAX) {
+                /* check if we need to split or call fnmatch() while matching rules */
+                enum string_glob_type glob;
+                int has_split;
+                int has_glob;
+
+                has_split = (strchr(value, '|') != NULL);
+                has_glob = (strchr(value, '*') != NULL || strchr(value, '?') != NULL || strchr(value, '[') != NULL);
+                if (has_split && has_glob) {
+                        glob = GL_SPLIT_GLOB;
+                } else if (has_split) {
+                        glob = GL_SPLIT;
+                } else if (has_glob) {
+                        if (strcmp(value, "?*") == 0)
+                                glob = GL_SOMETHING;
+                        else
+                                glob = GL_GLOB;
+                } else {
+                        glob = GL_PLAIN;
+                }
+                token->key.glob = glob;
+        }
+
+        if (value != NULL && type > TK_M_MAX) {
+                /* check if assigned value has substitution chars */
+                if (value[0] == '[')
+                        token->key.subst = SB_SUBSYS;
+                else if (strchr(value, '%') != NULL || strchr(value, '$') != NULL)
+                        token->key.subst = SB_FORMAT;
+                else
+                        token->key.subst = SB_NONE;
+        }
+
+        if (attr != NULL) {
+                /* check if property/attribut name has substitution chars */
+                if (attr[0] == '[')
+                        token->key.attrsubst = SB_SUBSYS;
+                else if (strchr(attr, '%') != NULL || strchr(attr, '$') != NULL)
+                        token->key.attrsubst = SB_FORMAT;
+                else
+                        token->key.attrsubst = SB_NONE;
+        }
+
+        token->key.type = type;
+        token->key.op = op;
+        rule_tmp->token_cur++;
+        if (rule_tmp->token_cur >= ARRAY_SIZE(rule_tmp->token)) {
+                err(rule_tmp->rules->udev, "temporary rule array too small\n");
+                return -1;
+        }
+        return 0;
+}
+
+static int sort_token(struct udev_rules *rules, struct rule_tmp *rule_tmp)
+{
+        unsigned int i;
+        unsigned int start = 0;
+        unsigned int end = rule_tmp->token_cur;
+
+        for (i = 0; i < rule_tmp->token_cur; i++) {
+                enum token_type next_val = TK_UNSET;
+                unsigned int next_idx = 0;
+                unsigned int j;
+
+                /* find smallest value */
+                for (j = start; j < end; j++) {
+                        if (rule_tmp->token[j].type == TK_UNSET)
+                                continue;
+                        if (next_val == TK_UNSET || rule_tmp->token[j].type < next_val) {
+                                next_val = rule_tmp->token[j].type;
+                                next_idx = j;
+                        }
+                }
+
+                /* add token and mark done */
+                if (add_token(rules, &rule_tmp->token[next_idx]) != 0)
+                        return -1;
+                rule_tmp->token[next_idx].type = TK_UNSET;
+
+                /* shrink range */
+                if (next_idx == start)
+                        start++;
+                if (next_idx+1 == end)
+                        end--;
+        }
+        return 0;
+}
+
+static int add_rule(struct udev_rules *rules, char *line,
+                    const char *filename, unsigned int filename_off, unsigned int lineno)
+{
+        char *linepos;
+        char *attr;
+        struct rule_tmp rule_tmp;
+
+        memset(&rule_tmp, 0x00, sizeof(struct rule_tmp));
+        rule_tmp.rules = rules;
+        rule_tmp.rule.type = TK_RULE;
+        rule_tmp.rule.rule.filename_off = filename_off;
+        rule_tmp.rule.rule.filename_line = lineno;
+
+        linepos = line;
+        for (;;) {
+                char *key;
+                char *value;
+                enum operation_type op;
+
+                if (get_key(rules->udev, &linepos, &key, &op, &value) != 0)
+                        break;
+
+                if (strcmp(key, "ACTION") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid ACTION operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_ACTION, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "DEVPATH") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid DEVPATH operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_DEVPATH, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "KERNEL") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid KERNEL operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_KERNEL, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "SUBSYSTEM") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid SUBSYSTEM operation\n");
+                                goto invalid;
+                        }
+                        /* bus, class, subsystem events should all be the same */
+                        if (strcmp(value, "subsystem") == 0 ||
+                            strcmp(value, "bus") == 0 ||
+                            strcmp(value, "class") == 0) {
+                                if (strcmp(value, "bus") == 0 || strcmp(value, "class") == 0)
+                                        err(rules->udev, "'%s' must be specified as 'subsystem' \n"
+                                            "please fix it in %s:%u", value, filename, lineno);
+                                rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, "subsystem|class|bus", NULL);
+                        } else
+                                rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "DRIVER") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid DRIVER operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_DRIVER, op, value, NULL);
+                        continue;
+                }
+
+                if (strncmp(key, "ATTR{", sizeof("ATTR{")-1) == 0) {
+                        attr = get_key_attribute(rules->udev, key + sizeof("ATTR")-1);
+                        if (attr == NULL) {
+                                err(rules->udev, "error parsing ATTR attribute\n");
+                                goto invalid;
+                        }
+                        if (op < OP_MATCH_MAX) {
+                                rule_add_key(&rule_tmp, TK_M_ATTR, op, value, attr);
+                        } else {
+                                rule_add_key(&rule_tmp, TK_A_ATTR, op, value, attr);
+                        }
+                        continue;
+                }
+
+                if (strcmp(key, "KERNELS") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid KERNELS operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_KERNELS, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "SUBSYSTEMS") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid SUBSYSTEMS operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_SUBSYSTEMS, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "DRIVERS") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid DRIVERS operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_DRIVERS, op, value, NULL);
+                        continue;
+                }
+
+                if (strncmp(key, "ATTRS{", sizeof("ATTRS{")-1) == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid ATTRS operation\n");
+                                goto invalid;
+                        }
+                        attr = get_key_attribute(rules->udev, key + sizeof("ATTRS")-1);
+                        if (attr == NULL) {
+                                err(rules->udev, "error parsing ATTRS attribute\n");
+                                goto invalid;
+                        }
+                        if (strncmp(attr, "device/", 7) == 0)
+                                err(rules->udev, "the 'device' link may not be available in a future kernel, "
+                                    "please fix it in %s:%u", filename, lineno);
+                        else if (strstr(attr, "../") != NULL)
+                                err(rules->udev, "do not reference parent sysfs directories directly, "
+                                    "it may break with a future kernel, please fix it in %s:%u", filename, lineno);
+                        rule_add_key(&rule_tmp, TK_M_ATTRS, op, value, attr);
+                        continue;
+                }
+
+                if (strcmp(key, "TAGS") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid TAGS operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_TAGS, op, value, NULL);
+                        continue;
+                }
+
+                if (strncmp(key, "ENV{", sizeof("ENV{")-1) == 0) {
+                        attr = get_key_attribute(rules->udev, key + sizeof("ENV")-1);
+                        if (attr == NULL) {
+                                err(rules->udev, "error parsing ENV attribute\n");
+                                goto invalid;
+                        }
+                        if (op < OP_MATCH_MAX) {
+                                if (rule_add_key(&rule_tmp, TK_M_ENV, op, value, attr) != 0)
+                                        goto invalid;
+                        } else {
+                                static const char *blacklist[] = {
+                                        "ACTION",
+                                        "SUBSYSTEM",
+                                        "DEVTYPE",
+                                        "MAJOR",
+                                        "MINOR",
+                                        "DRIVER",
+                                        "IFINDEX",
+                                        "DEVNAME",
+                                        "DEVLINKS",
+                                        "DEVPATH",
+                                        "TAGS",
+                                };
+                                unsigned int i;
+
+                                for (i = 0; i < ARRAY_SIZE(blacklist); i++)
+                                        if (strcmp(attr, blacklist[i]) == 0) {
+                                                err(rules->udev, "invalid ENV attribute, '%s' can not be set %s:%u\n", attr, filename, lineno);
+                                                continue;
+                                        }
+                                if (rule_add_key(&rule_tmp, TK_A_ENV, op, value, attr) != 0)
+                                        goto invalid;
+                        }
+                        continue;
+                }
+
+                if (strcmp(key, "TAG") == 0) {
+                        if (op < OP_MATCH_MAX)
+                                rule_add_key(&rule_tmp, TK_M_TAG, op, value, NULL);
+                        else
+                                rule_add_key(&rule_tmp, TK_A_TAG, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "PROGRAM") == 0) {
+                        rule_add_key(&rule_tmp, TK_M_PROGRAM, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "RESULT") == 0) {
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid RESULT operation\n");
+                                goto invalid;
+                        }
+                        rule_add_key(&rule_tmp, TK_M_RESULT, op, value, NULL);
+                        continue;
+                }
+
+                if (strncmp(key, "IMPORT", sizeof("IMPORT")-1) == 0) {
+                        attr = get_key_attribute(rules->udev, key + sizeof("IMPORT")-1);
+                        if (attr == NULL) {
+                                err(rules->udev, "IMPORT{} type missing, ignoring IMPORT %s:%u\n", filename, lineno);
+                                continue;
+                        }
+                        if (strstr(attr, "program")) {
+                                /* find known built-in command */
+                                if (value[0] != '/') {
+                                        enum udev_builtin_cmd cmd;
+
+                                        cmd = udev_builtin_lookup(value);
+                                        if (cmd < UDEV_BUILTIN_MAX) {
+                                                info(rules->udev, "IMPORT found builtin '%s', replacing %s:%u\n",
+                                                     value, filename, lineno);
+                                                rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd);
+                                                continue;
+                                        }
+                                }
+                                dbg(rules->udev, "IMPORT will be executed\n");
+                                rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL);
+                        } else if (strstr(attr, "builtin")) {
+                                enum udev_builtin_cmd cmd = udev_builtin_lookup(value);
+
+                                dbg(rules->udev, "IMPORT execute builtin\n");
+                                if (cmd < UDEV_BUILTIN_MAX)
+                                        rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd);
+                                else
+                                        err(rules->udev, "IMPORT{builtin}: '%s' unknown %s:%u\n", value, filename, lineno);
+                        } else if (strstr(attr, "file")) {
+                                dbg(rules->udev, "IMPORT will be included as file\n");
+                                rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL);
+                        } else if (strstr(attr, "db")) {
+                                dbg(rules->udev, "IMPORT will include db values\n");
+                                rule_add_key(&rule_tmp, TK_M_IMPORT_DB, op, value, NULL);
+                        } else if (strstr(attr, "cmdline")) {
+                                dbg(rules->udev, "IMPORT will include db values\n");
+                                rule_add_key(&rule_tmp, TK_M_IMPORT_CMDLINE, op, value, NULL);
+                        } else if (strstr(attr, "parent")) {
+                                dbg(rules->udev, "IMPORT will include the parent values\n");
+                                rule_add_key(&rule_tmp, TK_M_IMPORT_PARENT, op, value, NULL);
+                        }
+                        continue;
+                }
+
+                if (strncmp(key, "TEST", sizeof("TEST")-1) == 0) {
+                        mode_t mode = 0;
+
+                        if (op > OP_MATCH_MAX) {
+                                err(rules->udev, "invalid TEST operation\n");
+                                goto invalid;
+                        }
+                        attr = get_key_attribute(rules->udev, key + sizeof("TEST")-1);
+                        if (attr != NULL) {
+                                mode = strtol(attr, NULL, 8);
+                                rule_add_key(&rule_tmp, TK_M_TEST, op, value, &mode);
+                        } else {
+                                rule_add_key(&rule_tmp, TK_M_TEST, op, value, NULL);
+                        }
+                        continue;
+                }
+
+                if (strcmp(key, "RUN") == 0) {
+                        if (strncmp(value, "socket:", 7) == 0)
+                                err(rules->udev, "RUN+=\"socket:...\" support will be removed from a future udev release. "
+                                    "Please remove it from: %s:%u and use libudev to subscribe to events.\n", filename, lineno);
+                        rule_add_key(&rule_tmp, TK_A_RUN, op, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "WAIT_FOR") == 0 || strcmp(key, "WAIT_FOR_SYSFS") == 0) {
+                        rule_add_key(&rule_tmp, TK_M_WAITFOR, 0, value, NULL);
+                        continue;
+                }
+
+                if (strcmp(key, "LABEL") == 0) {
+                        rule_tmp.rule.rule.label_off = add_string(rules, value);
+                        continue;
+                }
+
+                if (strcmp(key, "GOTO") == 0) {
+                        rule_add_key(&rule_tmp, TK_A_GOTO, 0, value, NULL);
+                        continue;
+                }
+
+                if (strncmp(key, "NAME", sizeof("NAME")-1) == 0) {
+                        if (op < OP_MATCH_MAX) {
+                                rule_add_key(&rule_tmp, TK_M_NAME, op, value, NULL);
+                        } else {
+                                if (strcmp(value, "%k") == 0) {
+                                        err(rules->udev, "NAME=\"%%k\" is ignored, because it breaks kernel supplied names, "
+                                            "please remove it from %s:%u\n", filename, lineno);
+                                        continue;
+                                }
+                                if (value[0] == '\0') {
+                                        info(rules->udev, "NAME=\"\" is ignored, because udev will not delete any device nodes, "
+                                             "please remove it from %s:%u\n", filename, lineno);
+                                        continue;
+                                }
+                                rule_add_key(&rule_tmp, TK_A_NAME, op, value, NULL);
+                        }
+                        rule_tmp.rule.rule.can_set_name = true;
+                        continue;
+                }
+
+                if (strncmp(key, "SYMLINK", sizeof("SYMLINK")-1) == 0) {
+                        if (op < OP_MATCH_MAX) {
+                                rule_add_key(&rule_tmp, TK_M_DEVLINK, op, value, NULL);
+                        } else {
+                                int flag = 0;
+
+                                attr = get_key_attribute(rules->udev, key + sizeof("SYMLINK")-1);
+                                if (attr != NULL && strstr(attr, "unique") != NULL)
+                                        flag = 1;
+                                rule_add_key(&rule_tmp, TK_A_DEVLINK, op, value, &flag);
+                        }
+                        rule_tmp.rule.rule.can_set_name = true;
+                        continue;
+                }
+
+                if (strcmp(key, "OWNER") == 0) {
+                        uid_t uid;
+                        char *endptr;
+
+                        uid = strtoul(value, &endptr, 10);
+                        if (endptr[0] == '\0') {
+                                rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid);
+                        } else if ((rules->resolve_names > 0) && strchr("$%", value[0]) == NULL) {
+                                uid = add_uid(rules, value);
+                                rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid);
+                        } else if (rules->resolve_names >= 0) {
+                                rule_add_key(&rule_tmp, TK_A_OWNER, op, value, NULL);
+                        }
+                        rule_tmp.rule.rule.can_set_name = true;
+                        continue;
+                }
+
+                if (strcmp(key, "GROUP") == 0) {
+                        gid_t gid;
+                        char *endptr;
+
+                        gid = strtoul(value, &endptr, 10);
+                        if (endptr[0] == '\0') {
+                                rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid);
+                        } else if ((rules->resolve_names > 0) && strchr("$%", value[0]) == NULL) {
+                                gid = add_gid(rules, value);
+                                rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid);
+                        } else if (rules->resolve_names >= 0) {
+                                rule_add_key(&rule_tmp, TK_A_GROUP, op, value, NULL);
+                        }
+                        rule_tmp.rule.rule.can_set_name = true;
+                        continue;
+                }
+
+                if (strcmp(key, "MODE") == 0) {
+                        mode_t mode;
+                        char *endptr;
+
+                        mode = strtol(value, &endptr, 8);
+                        if (endptr[0] == '\0')
+                                rule_add_key(&rule_tmp, TK_A_MODE_ID, op, NULL, &mode);
+                        else
+                                rule_add_key(&rule_tmp, TK_A_MODE, op, value, NULL);
+                        rule_tmp.rule.rule.can_set_name = true;
+                        continue;
+                }
+
+                if (strcmp(key, "OPTIONS") == 0) {
+                        const char *pos;
+
+                        pos = strstr(value, "link_priority=");
+                        if (pos != NULL) {
+                                int prio = atoi(&pos[strlen("link_priority=")]);
+
+                                rule_add_key(&rule_tmp, TK_A_DEVLINK_PRIO, op, NULL, &prio);
+                                dbg(rules->udev, "link priority=%i\n", prio);
+                        }
+
+                        pos = strstr(value, "event_timeout=");
+                        if (pos != NULL) {
+                                int tout = atoi(&pos[strlen("event_timeout=")]);
+
+                                rule_add_key(&rule_tmp, TK_M_EVENT_TIMEOUT, op, NULL, &tout);
+                                dbg(rules->udev, "event timeout=%i\n", tout);
+                        }
+
+                        pos = strstr(value, "string_escape=");
+                        if (pos != NULL) {
+                                pos = &pos[strlen("string_escape=")];
+                                if (strncmp(pos, "none", strlen("none")) == 0)
+                                        rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_NONE, op, NULL, NULL);
+                                else if (strncmp(pos, "replace", strlen("replace")) == 0)
+                                        rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_REPLACE, op, NULL, NULL);
+                        }
+
+                        pos = strstr(value, "db_persist");
+                        if (pos != NULL)
+                                rule_add_key(&rule_tmp, TK_A_DB_PERSIST, op, NULL, NULL);
+
+                        pos = strstr(value, "nowatch");
+                        if (pos != NULL) {
+                                const int off = 0;
+
+                                rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &off);
+                                dbg(rules->udev, "inotify watch of device disabled\n");
+                        } else {
+                                pos = strstr(value, "watch");
+                                if (pos != NULL) {
+                                        const int on = 1;
+
+                                        rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &on);
+                                        dbg(rules->udev, "inotify watch of device requested\n");
+                                }
+                        }
+
+                        pos = strstr(value, "static_node=");
+                        if (pos != NULL) {
+                                rule_add_key(&rule_tmp, TK_A_STATIC_NODE, op, &pos[strlen("static_node=")], NULL);
+                                rule_tmp.rule.rule.has_static_node = true;
+                        }
+
+                        continue;
+                }
+
+                err(rules->udev, "unknown key '%s' in %s:%u\n", key, filename, lineno);
+                goto invalid;
+        }
+
+        /* add rule token */
+        rule_tmp.rule.rule.token_count = 1 + rule_tmp.token_cur;
+        if (add_token(rules, &rule_tmp.rule) != 0)
+                goto invalid;
+
+        /* add tokens to list, sorted by type */
+        if (sort_token(rules, &rule_tmp) != 0)
+                goto invalid;
+
+        return 0;
+invalid:
+        err(rules->udev, "invalid rule '%s:%u'\n", filename, lineno);
+        return -1;
+}
+
+static int parse_file(struct udev_rules *rules, const char *filename, unsigned short filename_off)
+{
+        FILE *f;
+        unsigned int first_token;
+        char line[UTIL_LINE_SIZE];
+        int line_nr = 0;
+        unsigned int i;
+
+        info(rules->udev, "reading '%s' as rules file\n", filename);
+
+        f = fopen(filename, "r");
+        if (f == NULL)
+                return -1;
+
+        first_token = rules->token_cur;
+
+        while (fgets(line, sizeof(line), f) != NULL) {
+                char *key;
+                size_t len;
+
+                /* skip whitespace */
+                line_nr++;
+                key = line;
+                while (isspace(key[0]))
+                        key++;
+
+                /* comment */
+                if (key[0] == '#')
+                        continue;
+
+                len = strlen(line);
+                if (len < 3)
+                        continue;
+
+                /* continue reading if backslash+newline is found */
+                while (line[len-2] == '\\') {
+                        if (fgets(&line[len-2], (sizeof(line)-len)+2, f) == NULL)
+                                break;
+                        if (strlen(&line[len-2]) < 2)
+                                break;
+                        line_nr++;
+                        len = strlen(line);
+                }
+
+                if (len+1 >= sizeof(line)) {
+                        err(rules->udev, "line too long '%s':%u, ignored\n", filename, line_nr);
+                        continue;
+                }
+                add_rule(rules, key, filename, filename_off, line_nr);
+        }
+        fclose(f);
+
+        /* link GOTOs to LABEL rules in this file to be able to fast-forward */
+        for (i = first_token+1; i < rules->token_cur; i++) {
+                if (rules->tokens[i].type == TK_A_GOTO) {
+                        char *label = &rules->buf[rules->tokens[i].key.value_off];
+                        unsigned int j;
+
+                        for (j = i+1; j < rules->token_cur; j++) {
+                                if (rules->tokens[j].type != TK_RULE)
+                                        continue;
+                                if (rules->tokens[j].rule.label_off == 0)
+                                        continue;
+                                if (strcmp(label, &rules->buf[rules->tokens[j].rule.label_off]) != 0)
+                                        continue;
+                                rules->tokens[i].key.rule_goto = j;
+                                break;
+                        }
+                        if (rules->tokens[i].key.rule_goto == 0)
+                                err(rules->udev, "GOTO '%s' has no matching label in: '%s'\n", label, filename);
+                }
+        }
+        return 0;
+}
+
+static int add_matching_files(struct udev *udev, struct udev_list *file_list, const char *dirname, const char *suffix)
+{
+        DIR *dir;
+        struct dirent *dent;
+        char filename[UTIL_PATH_SIZE];
+
+        dbg(udev, "open directory '%s'\n", dirname);
+        dir = opendir(dirname);
+        if (dir == NULL) {
+                info(udev, "unable to open '%s': %m\n", dirname);
+                return -1;
+        }
+
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                if (dent->d_name[0] == '.')
+                        continue;
+
+                /* look for file matching with specified suffix */
+                if (suffix != NULL) {
+                        const char *ext;
+
+                        ext = strrchr(dent->d_name, '.');
+                        if (ext == NULL)
+                                continue;
+                        if (strcmp(ext, suffix) != 0)
+                                continue;
+                }
+                util_strscpyl(filename, sizeof(filename), dirname, "/", dent->d_name, NULL);
+                dbg(udev, "put file '%s' into list\n", filename);
+                /*
+                 * the basename is the key, the filename the value
+                 * identical basenames from different directories override each other
+                 * entries are sorted after basename
+                 */
+                udev_list_entry_add(file_list, dent->d_name, filename);
+        }
+
+        closedir(dir);
+        return 0;
+}
+
+struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
+{
+        struct udev_rules *rules;
+        struct udev_list file_list;
+        struct udev_list_entry *file_loop;
+        struct token end_token;
+        char **s;
+
+        rules = calloc(1, sizeof(struct udev_rules));
+        if (rules == NULL)
+                return NULL;
+        rules->udev = udev;
+        rules->resolve_names = resolve_names;
+        udev_list_init(udev, &file_list, true);
+
+        /* init token array and string buffer */
+        rules->tokens = malloc(PREALLOC_TOKEN * sizeof(struct token));
+        if (rules->tokens == NULL) {
+                free(rules);
+                return NULL;
+        }
+        rules->token_max = PREALLOC_TOKEN;
+
+        rules->buf = malloc(PREALLOC_STRBUF);
+        if (rules->buf == NULL) {
+                free(rules->tokens);
+                free(rules);
+                return NULL;
+        }
+        rules->buf_max = PREALLOC_STRBUF;
+        /* offset 0 is always '\0' */
+        rules->buf[0] = '\0';
+        rules->buf_cur = 1;
+        dbg(udev, "prealloc %zu bytes tokens (%u * %zu bytes), %zu bytes buffer\n",
+            rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->buf_max);
+
+        rules->trie_nodes = malloc(PREALLOC_TRIE * sizeof(struct trie_node));
+        if (rules->trie_nodes == NULL) {
+                free(rules->buf);
+                free(rules->tokens);
+                free(rules);
+                return NULL;
+        }
+        rules->trie_nodes_max = PREALLOC_TRIE;
+        /* offset 0 is the trie root, with an empty string */
+        memset(rules->trie_nodes, 0x00, sizeof(struct trie_node));
+        rules->trie_nodes_cur = 1;
+
+        for (udev_get_rules_path(udev, &s, NULL); *s != NULL; s++)
+                add_matching_files(udev, &file_list, *s, ".rules");
+
+        /* add all filenames to the string buffer */
+        udev_list_entry_foreach(file_loop, udev_list_get_entry(&file_list)) {
+                const char *filename = udev_list_entry_get_value(file_loop);
+                unsigned int filename_off;
+
+                filename_off = add_string(rules, filename);
+                /* the offset in the rule is limited to unsigned short */
+                if (filename_off < USHRT_MAX)
+                        udev_list_entry_set_num(file_loop, filename_off);
+        }
+
+        /* parse all rules files */
+        udev_list_entry_foreach(file_loop, udev_list_get_entry(&file_list)) {
+                const char *filename = udev_list_entry_get_value(file_loop);
+                unsigned int filename_off = udev_list_entry_get_num(file_loop);
+                struct stat st;
+
+                if (stat(filename, &st) != 0) {
+                        err(udev, "can not find '%s': %m\n", filename);
+                        continue;
+                }
+                if (S_ISREG(st.st_mode) && st.st_size <= 0) {
+                        info(udev, "ignore empty '%s'\n", filename);
+                        continue;
+                }
+                if (S_ISCHR(st.st_mode)) {
+                        info(udev, "ignore masked '%s'\n", filename);
+                        continue;
+                }
+                parse_file(rules, filename, filename_off);
+        }
+        udev_list_cleanup(&file_list);
+
+        memset(&end_token, 0x00, sizeof(struct token));
+        end_token.type = TK_END;
+        add_token(rules, &end_token);
+
+        /* shrink allocated token and string buffer */
+        if (rules->token_cur < rules->token_max) {
+                struct token *tokens;
+
+                tokens = realloc(rules->tokens, rules->token_cur * sizeof(struct token));
+                if (tokens != NULL || rules->token_cur == 0) {
+                        rules->tokens = tokens;
+                        rules->token_max = rules->token_cur;
+                }
+        }
+        if (rules->buf_cur < rules->buf_max) {
+                char *buf;
+
+                buf = realloc(rules->buf, rules->buf_cur);
+                if (buf != NULL || rules->buf_cur == 0) {
+                        rules->buf = buf;
+                        rules->buf_max = rules->buf_cur;
+                }
+        }
+        info(udev, "rules use %zu bytes tokens (%u * %zu bytes), %zu bytes buffer\n",
+             rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->buf_max);
+        info(udev, "temporary index used %zu bytes (%u * %zu bytes)\n",
+             rules->trie_nodes_cur * sizeof(struct trie_node),
+             rules->trie_nodes_cur, sizeof(struct trie_node));
+
+        /* cleanup trie */
+        free(rules->trie_nodes);
+        rules->trie_nodes = NULL;
+        rules->trie_nodes_cur = 0;
+        rules->trie_nodes_max = 0;
+
+        /* cleanup uid/gid cache */
+        free(rules->uids);
+        rules->uids = NULL;
+        rules->uids_cur = 0;
+        rules->uids_max = 0;
+        free(rules->gids);
+        rules->gids = NULL;
+        rules->gids_cur = 0;
+        rules->gids_max = 0;
+
+        dump_rules(rules);
+        return rules;
+}
+
+struct udev_rules *udev_rules_unref(struct udev_rules *rules)
+{
+        if (rules == NULL)
+                return NULL;
+        free(rules->tokens);
+        free(rules->buf);
+        free(rules->trie_nodes);
+        free(rules->uids);
+        free(rules->gids);
+        free(rules);
+        return NULL;
+}
+
+static int match_key(struct udev_rules *rules, struct token *token, const char *val)
+{
+        char *key_value = &rules->buf[token->key.value_off];
+        char *pos;
+        bool match = false;
+
+        if (val == NULL)
+                val = "";
+
+        switch (token->key.glob) {
+        case GL_PLAIN:
+                match = (strcmp(key_value, val) == 0);
+                break;
+        case GL_GLOB:
+                match = (fnmatch(key_value, val, 0) == 0);
+                break;
+        case GL_SPLIT:
+                {
+                        const char *split;
+                        size_t len;
+
+                        split = &rules->buf[token->key.value_off];
+                        len = strlen(val);
+                        for (;;) {
+                                const char *next;
+
+                                next = strchr(split, '|');
+                                if (next != NULL) {
+                                        size_t matchlen = (size_t)(next - split);
+
+                                        match = (matchlen == len && strncmp(split, val, matchlen) == 0);
+                                        if (match)
+                                                break;
+                                } else {
+                                        match = (strcmp(split, val) == 0);
+                                        break;
+                                }
+                                split = &next[1];
+                        }
+                        break;
+                }
+        case GL_SPLIT_GLOB:
+                {
+                        char value[UTIL_PATH_SIZE];
+
+                        util_strscpy(value, sizeof(value), &rules->buf[token->key.value_off]);
+                        key_value = value;
+                        while (key_value != NULL) {
+                                pos = strchr(key_value, '|');
+                                if (pos != NULL) {
+                                        pos[0] = '\0';
+                                        pos = &pos[1];
+                                }
+                                dbg(rules->udev, "match %s '%s' <-> '%s'\n", token_str(token->type), key_value, val);
+                                match = (fnmatch(key_value, val, 0) == 0);
+                                if (match)
+                                        break;
+                                key_value = pos;
+                        }
+                        break;
+                }
+        case GL_SOMETHING:
+                match = (val[0] != '\0');
+                break;
+        case GL_UNSET:
+                return -1;
+        }
+
+        if (match && (token->key.op == OP_MATCH)) {
+                dbg(rules->udev, "%s is true (matching value)\n", token_str(token->type));
+                return 0;
+        }
+        if (!match && (token->key.op == OP_NOMATCH)) {
+                dbg(rules->udev, "%s is true (non-matching value)\n", token_str(token->type));
+                return 0;
+        }
+        dbg(rules->udev, "%s is not true\n", token_str(token->type));
+        return -1;
+}
+
+static int match_attr(struct udev_rules *rules, struct udev_device *dev, struct udev_event *event, struct token *cur)
+{
+        const char *name;
+        char nbuf[UTIL_NAME_SIZE];
+        const char *value;
+        char vbuf[UTIL_NAME_SIZE];
+        size_t len;
+
+        name = &rules->buf[cur->key.attr_off];
+        switch (cur->key.attrsubst) {
+        case SB_FORMAT:
+                udev_event_apply_format(event, name, nbuf, sizeof(nbuf));
+                name = nbuf;
+                /* fall through */
+        case SB_NONE:
+                value = udev_device_get_sysattr_value(dev, name);
+                if (value == NULL)
+                        return -1;
+                break;
+        case SB_SUBSYS:
+                if (util_resolve_subsys_kernel(event->udev, name, vbuf, sizeof(vbuf), 1) != 0)
+                        return -1;
+                value = vbuf;
+                break;
+        default:
+                return -1;
+        }
+
+        /* remove trailing whitespace, if not asked to match for it */
+        len = strlen(value);
+        if (len > 0 && isspace(value[len-1])) {
+                const char *key_value;
+                size_t klen;
+
+                key_value = &rules->buf[cur->key.value_off];
+                klen = strlen(key_value);
+                if (klen > 0 && !isspace(key_value[klen-1])) {
+                        if (value != vbuf) {
+                                util_strscpy(vbuf, sizeof(vbuf), value);
+                                value = vbuf;
+                        }
+                        while (len > 0 && isspace(vbuf[--len]))
+                                vbuf[len] = '\0';
+                        dbg(rules->udev, "removed trailing whitespace from '%s'\n", value);
+                }
+        }
+
+        return match_key(rules, cur, value);
+}
+
+enum escape_type {
+        ESCAPE_UNSET,
+        ESCAPE_NONE,
+        ESCAPE_REPLACE,
+};
+
+int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask)
+{
+        struct token *cur;
+        struct token *rule;
+        enum escape_type esc = ESCAPE_UNSET;
+        bool can_set_name;
+
+        if (rules->tokens == NULL)
+                return -1;
+
+        can_set_name = ((strcmp(udev_device_get_action(event->dev), "remove") != 0) &&
+                        (major(udev_device_get_devnum(event->dev)) > 0 ||
+                         udev_device_get_ifindex(event->dev) > 0));
+
+        /* loop through token list, match, run actions or forward to next rule */
+        cur = &rules->tokens[0];
+        rule = cur;
+        for (;;) {
+                dump_token(rules, cur);
+                switch (cur->type) {
+                case TK_RULE:
+                        /* current rule */
+                        rule = cur;
+                        /* possibly skip rules which want to set NAME, SYMLINK, OWNER, GROUP, MODE */
+                        if (!can_set_name && rule->rule.can_set_name)
+                                goto nomatch;
+                        esc = ESCAPE_UNSET;
+                        break;
+                case TK_M_ACTION:
+                        if (match_key(rules, cur, udev_device_get_action(event->dev)) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_DEVPATH:
+                        if (match_key(rules, cur, udev_device_get_devpath(event->dev)) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_KERNEL:
+                        if (match_key(rules, cur, udev_device_get_sysname(event->dev)) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_DEVLINK: {
+                        size_t devlen = strlen(udev_get_dev_path(event->udev))+1;
+                        struct udev_list_entry *list_entry;
+                        bool match = false;
+
+                        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(event->dev)) {
+                                const char *devlink;
+
+                                devlink =  &udev_list_entry_get_name(list_entry)[devlen];
+                                if (match_key(rules, cur, devlink) == 0) {
+                                        match = true;
+                                        break;
+                                }
+                        }
+                        if (!match)
+                                goto nomatch;
+                        break;
+                }
+                case TK_M_NAME:
+                        if (match_key(rules, cur, event->name) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_ENV: {
+                        const char *key_name = &rules->buf[cur->key.attr_off];
+                        const char *value;
+
+                        value = udev_device_get_property_value(event->dev, key_name);
+                        if (value == NULL) {
+                                dbg(event->udev, "ENV{%s} is not set, treat as empty\n", key_name);
+                                value = "";
+                        }
+                        if (match_key(rules, cur, value))
+                                goto nomatch;
+                        break;
+                }
+                case TK_M_TAG: {
+                        struct udev_list_entry *list_entry;
+                        bool match = false;
+
+                        udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(event->dev)) {
+                                if (strcmp(&rules->buf[cur->key.value_off], udev_list_entry_get_name(list_entry)) == 0) {
+                                        match = true;
+                                        break;
+                                }
+                        }
+                        if (!match && (cur->key.op != OP_NOMATCH))
+                                goto nomatch;
+                        break;
+                }
+                case TK_M_SUBSYSTEM:
+                        if (match_key(rules, cur, udev_device_get_subsystem(event->dev)) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_DRIVER:
+                        if (match_key(rules, cur, udev_device_get_driver(event->dev)) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_WAITFOR: {
+                        char filename[UTIL_PATH_SIZE];
+                        int found;
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], filename, sizeof(filename));
+                        found = (wait_for_file(event->dev, filename, 10) == 0);
+                        if (!found && (cur->key.op != OP_NOMATCH))
+                                goto nomatch;
+                        break;
+                }
+                case TK_M_ATTR:
+                        if (match_attr(rules, event->dev, event, cur) != 0)
+                                goto nomatch;
+                        break;
+                case TK_M_KERNELS:
+                case TK_M_SUBSYSTEMS:
+                case TK_M_DRIVERS:
+                case TK_M_ATTRS:
+                case TK_M_TAGS: {
+                        struct token *next;
+
+                        /* get whole sequence of parent matches */
+                        next = cur;
+                        while (next->type > TK_M_PARENTS_MIN && next->type < TK_M_PARENTS_MAX)
+                                next++;
+
+                        /* loop over parents */
+                        event->dev_parent = event->dev;
+                        for (;;) {
+                                struct token *key;
+
+                                dbg(event->udev, "parent: '%s'\n", udev_device_get_syspath(event->dev_parent));
+                                /* loop over sequence of parent match keys */
+                                for (key = cur; key < next; key++ ) {
+                                        dump_token(rules, key);
+                                        switch(key->type) {
+                                        case TK_M_KERNELS:
+                                                if (match_key(rules, key, udev_device_get_sysname(event->dev_parent)) != 0)
+                                                        goto try_parent;
+                                                break;
+                                        case TK_M_SUBSYSTEMS:
+                                                if (match_key(rules, key, udev_device_get_subsystem(event->dev_parent)) != 0)
+                                                        goto try_parent;
+                                                break;
+                                        case TK_M_DRIVERS:
+                                                if (match_key(rules, key, udev_device_get_driver(event->dev_parent)) != 0)
+                                                        goto try_parent;
+                                                break;
+                                        case TK_M_ATTRS:
+                                                if (match_attr(rules, event->dev_parent, event, key) != 0)
+                                                        goto try_parent;
+                                                break;
+                                        case TK_M_TAGS: {
+                                                bool match = udev_device_has_tag(event->dev_parent, &rules->buf[cur->key.value_off]);
+
+                                                if (match && key->key.op == OP_NOMATCH)
+                                                        goto try_parent;
+                                                if (!match && key->key.op == OP_MATCH)
+                                                        goto try_parent;
+                                                break;
+                                        }
+                                        default:
+                                                goto nomatch;
+                                        }
+                                        dbg(event->udev, "parent key matched\n");
+                                }
+                                dbg(event->udev, "all parent keys matched\n");
+                                break;
+
+                        try_parent:
+                                event->dev_parent = udev_device_get_parent(event->dev_parent);
+                                if (event->dev_parent == NULL)
+                                        goto nomatch;
+                        }
+                        /* move behind our sequence of parent match keys */
+                        cur = next;
+                        continue;
+                }
+                case TK_M_TEST: {
+                        char filename[UTIL_PATH_SIZE];
+                        struct stat statbuf;
+                        int match;
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], filename, sizeof(filename));
+                        if (util_resolve_subsys_kernel(event->udev, filename, filename, sizeof(filename), 0) != 0) {
+                                if (filename[0] != '/') {
+                                        char tmp[UTIL_PATH_SIZE];
+
+                                        util_strscpy(tmp, sizeof(tmp), filename);
+                                        util_strscpyl(filename, sizeof(filename),
+                                                      udev_device_get_syspath(event->dev), "/", tmp, NULL);
+                                }
+                        }
+                        attr_subst_subdir(filename, sizeof(filename));
+
+                        match = (stat(filename, &statbuf) == 0);
+                        dbg(event->udev, "'%s' %s", filename, match ? "exists\n" : "does not exist\n");
+                        if (match && cur->key.mode > 0) {
+                                match = ((statbuf.st_mode & cur->key.mode) > 0);
+                                dbg(event->udev, "'%s' has mode=%#o and %s %#o\n", filename, statbuf.st_mode,
+                                    match ? "matches" : "does not match", cur->key.mode);
+                        }
+                        if (match && cur->key.op == OP_NOMATCH)
+                                goto nomatch;
+                        if (!match && cur->key.op == OP_MATCH)
+                                goto nomatch;
+                        break;
+                }
+                case TK_M_EVENT_TIMEOUT:
+                        info(event->udev, "OPTIONS event_timeout=%u\n", cur->key.event_timeout);
+                        event->timeout_usec = cur->key.event_timeout * 1000 * 1000;
+                        break;
+                case TK_M_PROGRAM: {
+                        char program[UTIL_PATH_SIZE];
+                        char **envp;
+                        char result[UTIL_PATH_SIZE];
+
+                        free(event->program_result);
+                        event->program_result = NULL;
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], program, sizeof(program));
+                        envp = udev_device_get_properties_envp(event->dev);
+                        info(event->udev, "PROGRAM '%s' %s:%u\n",
+                             program,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+
+                        if (udev_event_spawn(event, program, envp, sigmask, result, sizeof(result)) < 0) {
+                                if (cur->key.op != OP_NOMATCH)
+                                        goto nomatch;
+                        } else {
+                                int count;
+
+                                util_remove_trailing_chars(result, '\n');
+                                if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+                                        count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT);
+                                        if (count > 0)
+                                                info(event->udev, "%i character(s) replaced\n" , count);
+                                }
+                                event->program_result = strdup(result);
+                                dbg(event->udev, "storing result '%s'\n", event->program_result);
+                                if (cur->key.op == OP_NOMATCH)
+                                        goto nomatch;
+                        }
+                        break;
+                }
+                case TK_M_IMPORT_FILE: {
+                        char import[UTIL_PATH_SIZE];
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], import, sizeof(import));
+                        if (import_file_into_properties(event->dev, import) != 0)
+                                if (cur->key.op != OP_NOMATCH)
+                                        goto nomatch;
+                        break;
+                }
+                case TK_M_IMPORT_PROG: {
+                        char import[UTIL_PATH_SIZE];
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], import, sizeof(import));
+                        info(event->udev, "IMPORT '%s' %s:%u\n",
+                             import,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+
+                        if (import_program_into_properties(event, import, sigmask) != 0)
+                                if (cur->key.op != OP_NOMATCH)
+                                        goto nomatch;
+                        break;
+                }
+                case TK_M_IMPORT_BUILTIN: {
+                        char command[UTIL_PATH_SIZE];
+
+                        if (udev_builtin_run_once(cur->key.builtin_cmd)) {
+                                /* check if we ran already */
+                                if (event->builtin_run & (1 << cur->key.builtin_cmd)) {
+                                        info(event->udev, "IMPORT builtin skip '%s' %s:%u\n",
+                                             udev_builtin_name(cur->key.builtin_cmd),
+                                             &rules->buf[rule->rule.filename_off],
+                                             rule->rule.filename_line);
+                                        /* return the result from earlier run */
+                                        if (event->builtin_ret & (1 << cur->key.builtin_cmd))
+                                        if (cur->key.op != OP_NOMATCH)
+                                                        goto nomatch;
+                                        break;
+                                }
+                                /* mark as ran */
+                                event->builtin_run |= (1 << cur->key.builtin_cmd);
+                        }
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], command, sizeof(command));
+                        info(event->udev, "IMPORT builtin '%s' %s:%u\n",
+                             udev_builtin_name(cur->key.builtin_cmd),
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+
+                        if (udev_builtin_run(event->dev, cur->key.builtin_cmd, command, false) != 0) {
+                                /* remember failure */
+                                info(rules->udev, "IMPORT builtin '%s' returned non-zero\n",
+                                     udev_builtin_name(cur->key.builtin_cmd));
+                                event->builtin_ret |= (1 << cur->key.builtin_cmd);
+                                if (cur->key.op != OP_NOMATCH)
+                                        goto nomatch;
+                        }
+                        break;
+                }
+                case TK_M_IMPORT_DB: {
+                        const char *key = &rules->buf[cur->key.value_off];
+                        const char *value;
+
+                        value = udev_device_get_property_value(event->dev_db, key);
+                        if (value != NULL) {
+                                struct udev_list_entry *entry;
+
+                                entry = udev_device_add_property(event->dev, key, value);
+                                udev_list_entry_set_num(entry, true);
+                        } else {
+                                if (cur->key.op != OP_NOMATCH)
+                                        goto nomatch;
+                        }
+                        break;
+                }
+                case TK_M_IMPORT_CMDLINE: {
+                        FILE *f;
+                        bool imported = false;
+
+                        f = fopen("/proc/cmdline", "r");
+                        if (f != NULL) {
+                                char cmdline[4096];
+
+                                if (fgets(cmdline, sizeof(cmdline), f) != NULL) {
+                                        const char *key = &rules->buf[cur->key.value_off];
+                                        char *pos;
+
+                                        pos = strstr(cmdline, key);
+                                        if (pos != NULL) {
+                                                struct udev_list_entry *entry;
+
+                                                pos += strlen(key);
+                                                if (pos[0] == '\0' || isspace(pos[0])) {
+                                                        /* we import simple flags as 'FLAG=1' */
+                                                        entry = udev_device_add_property(event->dev, key, "1");
+                                                        udev_list_entry_set_num(entry, true);
+                                                        imported = true;
+                                                } else if (pos[0] == '=') {
+                                                        const char *value;
+
+                                                        pos++;
+                                                        value = pos;
+                                                        while (pos[0] != '\0' && !isspace(pos[0]))
+                                                                pos++;
+                                                        pos[0] = '\0';
+                                                        entry = udev_device_add_property(event->dev, key, value);
+                                                        udev_list_entry_set_num(entry, true);
+                                                        imported = true;
+                                                }
+                                        }
+                                }
+                                fclose(f);
+                        }
+                        if (!imported && cur->key.op != OP_NOMATCH)
+                                goto nomatch;
+                        break;
+                }
+                case TK_M_IMPORT_PARENT: {
+                        char import[UTIL_PATH_SIZE];
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], import, sizeof(import));
+                        if (import_parent_into_properties(event->dev, import) != 0)
+                                if (cur->key.op != OP_NOMATCH)
+                                        goto nomatch;
+                        break;
+                }
+                case TK_M_RESULT:
+                        if (match_key(rules, cur, event->program_result) != 0)
+                                goto nomatch;
+                        break;
+                case TK_A_STRING_ESCAPE_NONE:
+                        esc = ESCAPE_NONE;
+                        break;
+                case TK_A_STRING_ESCAPE_REPLACE:
+                        esc = ESCAPE_REPLACE;
+                        break;
+                case TK_A_DB_PERSIST:
+                        udev_device_set_db_persist(event->dev);
+                        break;
+                case TK_A_INOTIFY_WATCH:
+                        if (event->inotify_watch_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->inotify_watch_final = true;
+                        event->inotify_watch = cur->key.watch;
+                        break;
+                case TK_A_DEVLINK_PRIO:
+                        udev_device_set_devlink_priority(event->dev, cur->key.devlink_prio);
+                        break;
+                case TK_A_OWNER: {
+                        char owner[UTIL_NAME_SIZE];
+
+                        if (event->owner_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->owner_final = true;
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], owner, sizeof(owner));
+                        event->uid = util_lookup_user(event->udev, owner);
+                        info(event->udev, "OWNER %u %s:%u\n",
+                             event->uid,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                }
+                case TK_A_GROUP: {
+                        char group[UTIL_NAME_SIZE];
+
+                        if (event->group_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->group_final = true;
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], group, sizeof(group));
+                        event->gid = util_lookup_group(event->udev, group);
+                        info(event->udev, "GROUP %u %s:%u\n",
+                             event->gid,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                }
+                case TK_A_MODE: {
+                        char mode_str[UTIL_NAME_SIZE];
+                        mode_t mode;
+                        char *endptr;
+
+                        if (event->mode_final)
+                                break;
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], mode_str, sizeof(mode_str));
+                        mode = strtol(mode_str, &endptr, 8);
+                        if (endptr[0] != '\0') {
+                                err(event->udev, "ignoring invalid mode '%s'\n", mode_str);
+                                break;
+                        }
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->mode_final = true;
+                        event->mode_set = true;
+                        event->mode = mode;
+                        info(event->udev, "MODE %#o %s:%u\n",
+                             event->mode,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                }
+                case TK_A_OWNER_ID:
+                        if (event->owner_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->owner_final = true;
+                        event->uid = cur->key.uid;
+                        info(event->udev, "OWNER %u %s:%u\n",
+                             event->uid,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                case TK_A_GROUP_ID:
+                        if (event->group_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->group_final = true;
+                        event->gid = cur->key.gid;
+                        info(event->udev, "GROUP %u %s:%u\n",
+                             event->gid,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                case TK_A_MODE_ID:
+                        if (event->mode_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->mode_final = true;
+                        event->mode_set = true;
+                        event->mode = cur->key.mode;
+                        info(event->udev, "MODE %#o %s:%u\n",
+                             event->mode,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                case TK_A_ENV: {
+                        const char *name = &rules->buf[cur->key.attr_off];
+                        char *value = &rules->buf[cur->key.value_off];
+
+                        if (value[0] != '\0') {
+                                char temp_value[UTIL_NAME_SIZE];
+                                struct udev_list_entry *entry;
+
+                                udev_event_apply_format(event, value, temp_value, sizeof(temp_value));
+                                entry = udev_device_add_property(event->dev, name, temp_value);
+                                /* store in db, skip private keys */
+                                if (name[0] != '.')
+                                        udev_list_entry_set_num(entry, true);
+                        } else {
+                                udev_device_add_property(event->dev, name, NULL);
+                        }
+                        break;
+                }
+                case TK_A_TAG: {
+                        char tag[UTIL_PATH_SIZE];
+                        const char *p;
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], tag, sizeof(tag));
+                        if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+                                udev_device_cleanup_tags_list(event->dev);
+                        for (p = tag; *p != '\0'; p++) {
+                                if ((*p >= 'a' && *p <= 'z') ||
+                                    (*p >= 'A' && *p <= 'Z') ||
+                                    (*p >= '0' && *p <= '9') ||
+                                    *p == '-' || *p == '_')
+                                        continue;
+                                err(event->udev, "ignoring invalid tag name '%s'\n", tag);
+                                break;
+                        }
+                        udev_device_add_tag(event->dev, tag);
+                        break;
+                }
+                case TK_A_NAME: {
+                        const char *name  = &rules->buf[cur->key.value_off];
+
+                        char name_str[UTIL_PATH_SIZE];
+                        int count;
+
+                        if (event->name_final)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->name_final = true;
+                        udev_event_apply_format(event, name, name_str, sizeof(name_str));
+                        if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+                                count = util_replace_chars(name_str, "/");
+                                if (count > 0)
+                                        info(event->udev, "%i character(s) replaced\n", count);
+                        }
+                        if (major(udev_device_get_devnum(event->dev))) {
+                                size_t devlen = strlen(udev_get_dev_path(event->udev))+1;
+
+                                if (strcmp(name_str, &udev_device_get_devnode(event->dev)[devlen]) != 0) {
+                                        err(event->udev, "NAME=\"%s\" ignored, kernel device nodes "
+                                            "can not be renamed; please fix it in %s:%u\n", name,
+                                            &rules->buf[rule->rule.filename_off], rule->rule.filename_line);
+                                        break;
+                                }
+                        }
+                        free(event->name);
+                        event->name = strdup(name_str);
+                        info(event->udev, "NAME '%s' %s:%u\n",
+                             event->name,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        break;
+                }
+                case TK_A_DEVLINK: {
+                        char temp[UTIL_PATH_SIZE];
+                        char filename[UTIL_PATH_SIZE];
+                        char *pos, *next;
+                        int count = 0;
+
+                        if (event->devlink_final)
+                                break;
+                        if (major(udev_device_get_devnum(event->dev)) == 0)
+                                break;
+                        if (cur->key.op == OP_ASSIGN_FINAL)
+                                event->devlink_final = true;
+                        if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+                                udev_device_cleanup_devlinks_list(event->dev);
+
+                        /* allow  multiple symlinks separated by spaces */
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], temp, sizeof(temp));
+                        if (esc == ESCAPE_UNSET)
+                                count = util_replace_chars(temp, "/ ");
+                        else if (esc == ESCAPE_REPLACE)
+                                count = util_replace_chars(temp, "/");
+                        if (count > 0)
+                                info(event->udev, "%i character(s) replaced\n" , count);
+                        dbg(event->udev, "rule applied, added symlink(s) '%s'\n", temp);
+                        pos = temp;
+                        while (isspace(pos[0]))
+                                pos++;
+                        next = strchr(pos, ' ');
+                        while (next != NULL) {
+                                next[0] = '\0';
+                                info(event->udev, "LINK '%s' %s:%u\n", pos,
+                                     &rules->buf[rule->rule.filename_off], rule->rule.filename_line);
+                                util_strscpyl(filename, sizeof(filename), udev_get_dev_path(event->udev), "/", pos, NULL);
+                                udev_device_add_devlink(event->dev, filename, cur->key.devlink_unique);
+                                while (isspace(next[1]))
+                                        next++;
+                                pos = &next[1];
+                                next = strchr(pos, ' ');
+                        }
+                        if (pos[0] != '\0') {
+                                info(event->udev, "LINK '%s' %s:%u\n", pos,
+                                     &rules->buf[rule->rule.filename_off], rule->rule.filename_line);
+                                util_strscpyl(filename, sizeof(filename), udev_get_dev_path(event->udev), "/", pos, NULL);
+                                udev_device_add_devlink(event->dev, filename, cur->key.devlink_unique);
+                        }
+                        break;
+                }
+                case TK_A_ATTR: {
+                        const char *key_name = &rules->buf[cur->key.attr_off];
+                        char attr[UTIL_PATH_SIZE];
+                        char value[UTIL_NAME_SIZE];
+                        FILE *f;
+
+                        if (util_resolve_subsys_kernel(event->udev, key_name, attr, sizeof(attr), 0) != 0)
+                                util_strscpyl(attr, sizeof(attr), udev_device_get_syspath(event->dev), "/", key_name, NULL);
+                        attr_subst_subdir(attr, sizeof(attr));
+
+                        udev_event_apply_format(event, &rules->buf[cur->key.value_off], value, sizeof(value));
+                        info(event->udev, "ATTR '%s' writing '%s' %s:%u\n", attr, value,
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        f = fopen(attr, "w");
+                        if (f != NULL) {
+                                if (fprintf(f, "%s", value) <= 0)
+                                        err(event->udev, "error writing ATTR{%s}: %m\n", attr);
+                                fclose(f);
+                        } else {
+                                err(event->udev, "error opening ATTR{%s} for writing: %m\n", attr);
+                        }
+                        break;
+                }
+                case TK_A_RUN: {
+                        if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+                                udev_list_cleanup(&event->run_list);
+                        info(event->udev, "RUN '%s' %s:%u\n",
+                             &rules->buf[cur->key.value_off],
+                             &rules->buf[rule->rule.filename_off],
+                             rule->rule.filename_line);
+                        udev_list_entry_add(&event->run_list, &rules->buf[cur->key.value_off], NULL);
+                        break;
+                }
+                case TK_A_GOTO:
+                        if (cur->key.rule_goto == 0)
+                                break;
+                        cur = &rules->tokens[cur->key.rule_goto];
+                        continue;
+                case TK_END:
+                        return 0;
+
+                case TK_M_PARENTS_MIN:
+                case TK_M_PARENTS_MAX:
+                case TK_M_MAX:
+                case TK_UNSET:
+                        err(rules->udev, "wrong type %u\n", cur->type);
+                        goto nomatch;
+                }
+
+                cur++;
+                continue;
+        nomatch:
+                /* fast-forward to next rule */
+                cur = rule + rule->rule.token_count;
+                dbg(rules->udev, "forward to rule: %u\n",
+                                 (unsigned int) (cur - rules->tokens));
+        }
+}
+
+void udev_rules_apply_static_dev_perms(struct udev_rules *rules)
+{
+        struct token *cur;
+        struct token *rule;
+        uid_t uid = 0;
+        gid_t gid = 0;
+        mode_t mode = 0;
+
+        if (rules->tokens == NULL)
+                return;
+
+        cur = &rules->tokens[0];
+        rule = cur;
+        for (;;) {
+                switch (cur->type) {
+                case TK_RULE:
+                        /* current rule */
+                        rule = cur;
+
+                        /* skip rules without a static_node tag */
+                        if (!rule->rule.has_static_node)
+                                goto next;
+
+                        uid = 0;
+                        gid = 0;
+                        mode = 0;
+                        break;
+                case TK_A_OWNER_ID:
+                        uid = cur->key.uid;
+                        break;
+                case TK_A_GROUP_ID:
+                        gid = cur->key.gid;
+                        break;
+                case TK_A_MODE_ID:
+                        mode = cur->key.mode;
+                        break;
+                case TK_A_STATIC_NODE: {
+                        char filename[UTIL_PATH_SIZE];
+                        struct stat stats;
+
+                        /* we assure, that the permissions tokens are sorted before the static token */
+                        if (mode == 0 && uid == 0 && gid == 0)
+                                goto next;
+                        util_strscpyl(filename, sizeof(filename), udev_get_dev_path(rules->udev), "/",
+                                      &rules->buf[cur->key.value_off], NULL);
+                        if (stat(filename, &stats) != 0)
+                                goto next;
+                        if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode))
+                                goto next;
+                        if (mode == 0) {
+                                if (gid > 0)
+                                        mode = 0660;
+                                else
+                                        mode = 0600;
+                        }
+                        if (mode != (stats.st_mode & 01777)) {
+                                chmod(filename, mode);
+                                info(rules->udev, "chmod '%s' %#o\n", filename, mode);
+                        }
+
+                        if ((uid != 0 && uid != stats.st_uid) || (gid != 0 && gid != stats.st_gid)) {
+                                chown(filename, uid, gid);
+                                info(rules->udev, "chown '%s' %u %u\n", filename, uid, gid);
+                        }
+
+                        utimensat(AT_FDCWD, filename, NULL, 0);
+                        break;
+                }
+                case TK_END:
+                        return;
+                }
+
+                cur++;
+                continue;
+next:
+                /* fast-forward to next rule */
+                cur = rule + rule->rule.token_count;
+                continue;
+        }
+}
diff --git a/src/udev-settle.service.in b/src/udev-settle.service.in
new file mode 100644
index 0000000..b0a4964
--- /dev/null
+++ b/src/udev-settle.service.in
@@ -0,0 +1,25 @@
+# This service is usually not enabled by default. If enabled, it
+# acts as a barrier for basic.target -- so all later services will
+# wait for udev completely finishing its coldplug run.
+#
+# If needed, to work around broken or non-hotplug-aware services,
+# it might be enabled unconditionally, or pulled-in on-demand by
+# the services that assume a fully populated /dev at startup. It
+# should not be used or pulled-in ever on systems without such
+# legacy services running.
+
+[Unit]
+Description=udev Wait for Complete Device Initialization
+DefaultDependencies=no
+Wants=udev.service
+After=udev-trigger.service
+Before=basic.target
+
+[Service]
+Type=oneshot
+TimeoutSec=180
+RemainAfterExit=yes
+ExecStart=@bindir@/udevadm settle
+
+[Install]
+WantedBy=basic.target
diff --git a/src/udev-trigger.service.in b/src/udev-trigger.service.in
new file mode 100644
index 0000000..cd81945
--- /dev/null
+++ b/src/udev-trigger.service.in
@@ -0,0 +1,10 @@
+[Unit]
+Description=udev Coldplug all Devices
+Wants=udev.service
+After=udev-kernel.socket udev-control.socket
+DefaultDependencies=no
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@bindir@/udevadm trigger --type=subsystems --action=add ; @bindir@/udevadm trigger --type=devices --action=add
diff --git a/src/udev-watch.c b/src/udev-watch.c
new file mode 100644
index 0000000..228d18f
--- /dev/null
+++ b/src/udev-watch.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2004-2010 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/inotify.h>
+
+#include "udev.h"
+
+static int inotify_fd = -1;
+
+/* inotify descriptor, will be shared with rules directory;
+ * set to cloexec since we need our children to be able to add
+ * watches for us
+ */
+int udev_watch_init(struct udev *udev)
+{
+        inotify_fd = inotify_init1(IN_CLOEXEC);
+        if (inotify_fd < 0)
+                err(udev, "inotify_init failed: %m\n");
+        return inotify_fd;
+}
+
+/* move any old watches directory out of the way, and then restore
+ * the watches
+ */
+void udev_watch_restore(struct udev *udev)
+{
+        char filename[UTIL_PATH_SIZE], oldname[UTIL_PATH_SIZE];
+
+        if (inotify_fd < 0)
+                return;
+
+        util_strscpyl(oldname, sizeof(oldname), udev_get_run_path(udev), "/watch.old", NULL);
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/watch", NULL);
+        if (rename(filename, oldname) == 0) {
+                DIR *dir;
+                struct dirent *ent;
+
+                dir = opendir(oldname);
+                if (dir == NULL) {
+                        err(udev, "unable to open old watches dir '%s', old watches will not be restored: %m", oldname);
+                        return;
+                }
+
+                for (ent = readdir(dir); ent != NULL; ent = readdir(dir)) {
+                        char device[UTIL_PATH_SIZE];
+                        char *s;
+                        size_t l;
+                        ssize_t len;
+                        struct udev_device *dev;
+
+                        if (ent->d_name[0] == '.')
+                                continue;
+
+                        s = device;
+                        l = util_strpcpy(&s, sizeof(device), udev_get_sys_path(udev));
+                        len = readlinkat(dirfd(dir), ent->d_name, s, l);
+                        if (len <= 0 || len == (ssize_t)l)
+                                goto unlink;
+                        s[len] = '\0';
+
+                        dev = udev_device_new_from_id_filename(udev, s);
+                        if (dev == NULL)
+                                goto unlink;
+
+                        info(udev, "restoring old watch on '%s'\n", udev_device_get_devnode(dev));
+                        udev_watch_begin(udev, dev);
+                        udev_device_unref(dev);
+unlink:
+                        unlinkat(dirfd(dir), ent->d_name, 0);
+                }
+
+                closedir(dir);
+                rmdir(oldname);
+
+        } else if (errno != ENOENT) {
+                err(udev, "unable to move watches dir '%s', old watches will not be restored: %m", filename);
+        }
+}
+
+void udev_watch_begin(struct udev *udev, struct udev_device *dev)
+{
+        char filename[UTIL_PATH_SIZE];
+        int wd;
+
+        if (inotify_fd < 0)
+                return;
+
+        info(udev, "adding watch on '%s'\n", udev_device_get_devnode(dev));
+        wd = inotify_add_watch(inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE);
+        if (wd < 0) {
+                err(udev, "inotify_add_watch(%d, %s, %o) failed: %m\n",
+                    inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE);
+                return;
+        }
+
+        snprintf(filename, sizeof(filename), "%s/watch/%d", udev_get_run_path(udev), wd);
+        util_create_path(udev, filename);
+        unlink(filename);
+        symlink(udev_device_get_id_filename(dev), filename);
+
+        udev_device_set_watch_handle(dev, wd);
+}
+
+void udev_watch_end(struct udev *udev, struct udev_device *dev)
+{
+        int wd;
+        char filename[UTIL_PATH_SIZE];
+
+        if (inotify_fd < 0)
+                return;
+
+        wd = udev_device_get_watch_handle(dev);
+        if (wd < 0)
+                return;
+
+        info(udev, "removing watch on '%s'\n", udev_device_get_devnode(dev));
+        inotify_rm_watch(inotify_fd, wd);
+
+        snprintf(filename, sizeof(filename), "%s/watch/%d", udev_get_run_path(udev), wd);
+        unlink(filename);
+
+        udev_device_set_watch_handle(dev, -1);
+}
+
+struct udev_device *udev_watch_lookup(struct udev *udev, int wd)
+{
+        char filename[UTIL_PATH_SIZE];
+        char majmin[UTIL_PATH_SIZE];
+        char *s;
+        size_t l;
+        ssize_t len;
+
+        if (inotify_fd < 0 || wd < 0)
+                return NULL;
+
+        snprintf(filename, sizeof(filename), "%s/watch/%d", udev_get_run_path(udev), wd);
+        s = majmin;
+        l = util_strpcpy(&s, sizeof(majmin), udev_get_sys_path(udev));
+        len = readlink(filename, s, l);
+        if (len <= 0 || (size_t)len == l)
+                return NULL;
+        s[len] = '\0';
+
+        return udev_device_new_from_id_filename(udev, s);
+}
diff --git a/src/udev.conf b/src/udev.conf
new file mode 100644
index 0000000..f39253e
--- /dev/null
+++ b/src/udev.conf
@@ -0,0 +1,3 @@
+# see udev(7) for details
+
+#udev_log="info"
diff --git a/src/udev.h b/src/udev.h
new file mode 100644
index 0000000..bc051c9
--- /dev/null
+++ b/src/udev.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2003-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UDEV_H_
+#define _UDEV_H_
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <signal.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+struct udev_event {
+        struct udev *udev;
+        struct udev_device *dev;
+        struct udev_device *dev_parent;
+        struct udev_device *dev_db;
+        char *name;
+        char *program_result;
+        mode_t mode;
+        uid_t uid;
+        gid_t gid;
+        struct udev_list run_list;
+        int exec_delay;
+        unsigned long long birth_usec;
+        unsigned long long timeout_usec;
+        int fd_signal;
+        unsigned int builtin_run;
+        unsigned int builtin_ret;
+        bool sigterm;
+        bool inotify_watch;
+        bool inotify_watch_final;
+        bool group_final;
+        bool owner_final;
+        bool mode_set;
+        bool mode_final;
+        bool name_final;
+        bool devlink_final;
+        bool run_final;
+};
+
+struct udev_watch {
+        struct udev_list_node node;
+        int handle;
+        char *name;
+};
+
+/* udev-rules.c */
+struct udev_rules;
+struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names);
+struct udev_rules *udev_rules_unref(struct udev_rules *rules);
+int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask);
+void udev_rules_apply_static_dev_perms(struct udev_rules *rules);
+
+/* udev-event.c */
+struct udev_event *udev_event_new(struct udev_device *dev);
+void udev_event_unref(struct udev_event *event);
+size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size);
+int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string,
+                                   char *result, size_t maxsize, int read_value);
+int udev_event_spawn(struct udev_event *event,
+                     const char *cmd, char **envp, const sigset_t *sigmask,
+                     char *result, size_t ressize);
+int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules, const sigset_t *sigset);
+int udev_event_execute_run(struct udev_event *event, const sigset_t *sigset);
+int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]);
+
+/* udev-watch.c */
+int udev_watch_init(struct udev *udev);
+void udev_watch_restore(struct udev *udev);
+void udev_watch_begin(struct udev *udev, struct udev_device *dev);
+void udev_watch_end(struct udev *udev, struct udev_device *dev);
+struct udev_device *udev_watch_lookup(struct udev *udev, int wd);
+
+/* udev-node.c */
+void udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid);
+void udev_node_remove(struct udev_device *dev);
+void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old);
+
+/* udev-ctrl.c */
+struct udev_ctrl;
+struct udev_ctrl *udev_ctrl_new(struct udev *udev);
+struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd);
+int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl);
+struct udev_ctrl *udev_ctrl_ref(struct udev_ctrl *uctrl);
+struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl);
+int udev_ctrl_cleanup(struct udev_ctrl *uctrl);
+struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl);
+int udev_ctrl_get_fd(struct udev_ctrl *uctrl);
+int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout);
+int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout);
+int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout);
+int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout);
+int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout);
+int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout);
+int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout);
+int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout);
+struct udev_ctrl_connection;
+struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl);
+struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn);
+struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn);
+struct udev_ctrl_msg;
+struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn);
+struct udev_ctrl_msg *udev_ctrl_msg_ref(struct udev_ctrl_msg *ctrl_msg);
+struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg);
+const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg);
+int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg);
+
+/* built-in commands */
+enum udev_builtin_cmd {
+        UDEV_BUILTIN_BLKID,
+        UDEV_BUILTIN_FIRMWARE,
+        UDEV_BUILTIN_INPUT_ID,
+        UDEV_BUILTIN_KMOD,
+        UDEV_BUILTIN_PATH_ID,
+        UDEV_BUILTIN_PCI_DB,
+        UDEV_BUILTIN_USB_DB,
+        UDEV_BUILTIN_USB_ID,
+        UDEV_BUILTIN_MAX
+};
+struct udev_builtin {
+        const char *name;
+        int (*cmd)(struct udev_device *dev, int argc, char *argv[], bool test);
+        const char *help;
+        int (*init)(struct udev *udev);
+        void (*exit)(struct udev *udev);
+        bool (*validate)(struct udev *udev);
+        bool run_once;
+};
+extern const struct udev_builtin udev_builtin_blkid;
+extern const struct udev_builtin udev_builtin_firmware;
+extern const struct udev_builtin udev_builtin_input_id;
+extern const struct udev_builtin udev_builtin_kmod;
+extern const struct udev_builtin udev_builtin_path_id;
+extern const struct udev_builtin udev_builtin_pci_db;
+extern const struct udev_builtin udev_builtin_usb_db;
+extern const struct udev_builtin udev_builtin_usb_id;
+int udev_builtin_init(struct udev *udev);
+void udev_builtin_exit(struct udev *udev);
+enum udev_builtin_cmd udev_builtin_lookup(const char *command);
+const char *udev_builtin_name(enum udev_builtin_cmd cmd);
+bool udev_builtin_run_once(enum udev_builtin_cmd cmd);
+int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test);
+void udev_builtin_list(struct udev *udev);
+int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val);
+
+/* udev logging */
+void udev_main_log(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args);
+
+/* udevadm commands */
+struct udevadm_cmd {
+        const char *name;
+        int (*cmd)(struct udev *udev, int argc, char *argv[]);
+        const char *help;
+        int debug;
+};
+extern const struct udevadm_cmd udevadm_info;
+extern const struct udevadm_cmd udevadm_trigger;
+extern const struct udevadm_cmd udevadm_settle;
+extern const struct udevadm_cmd udevadm_control;
+extern const struct udevadm_cmd udevadm_monitor;
+extern const struct udevadm_cmd udevadm_test;
+extern const struct udevadm_cmd udevadm_test_builtin;
+#endif
diff --git a/src/udev.pc.in b/src/udev.pc.in
new file mode 100644
index 0000000..0b04c02
--- /dev/null
+++ b/src/udev.pc.in
@@ -0,0 +1,5 @@
+Name: udev
+Description: udev
+Version: @VERSION@
+
+udevdir=@pkglibexecdir@
diff --git a/src/udev.service.in b/src/udev.service.in
new file mode 100644
index 0000000..c27eb1b
--- /dev/null
+++ b/src/udev.service.in
@@ -0,0 +1,14 @@
+[Unit]
+Description=udev Kernel Device Manager
+Wants=udev-control.socket udev-kernel.socket
+After=udev-control.socket udev-kernel.socket
+Before=basic.target
+DefaultDependencies=no
+ConditionCapability=CAP_MKNOD
+
+[Service]
+Type=notify
+OOMScoreAdjust=-1000
+Sockets=udev-control.socket udev-kernel.socket
+Restart=on-failure
+ExecStart=@pkglibexecdir@/udevd
diff --git a/src/udev.xml b/src/udev.xml
new file mode 100644
index 0000000..8eb583a
--- /dev/null
+++ b/src/udev.xml
@@ -0,0 +1,695 @@
+<?xml version='1.0'?>
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="udev">
+  <refentryinfo>
+    <title>udev</title>
+    <productname>udev</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>udev</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>udev</refname>
+    <refpurpose>Linux dynamic device management</refpurpose>
+  </refnamediv>
+
+  <refsect1><title>Description</title>
+    <para>udev supplies the system software with device events, manages permissions
+    of device nodes and may create additional symlinks in the <filename>/dev</filename>
+    directory, or renames network interfaces. The kernel usually just assigns unpredictable
+    device names based on the order of discovery. Meaningful symlinks or network device
+    names provide a way to reliably identify devices based on their properties or
+    current configuration.</para>
+
+    <para>The udev daemon, <citerefentry><refentrytitle>udevd</refentrytitle>
+    <manvolnum>8</manvolnum></citerefentry>, receives device uevents directly from
+    the kernel whenever a device is added or removed from the system, or it changes its
+    state. When udev receives a device event, it matches its configured set of rules
+    against various device attributes to identify the device. Rules that match may
+    provide additional device information to be stored in the udev database or
+    to be used to create meaningful symlink names.</para>
+
+    <para>All device information udev processes is stored in the udev database and
+    sent out to possible event subscribers. Access to all stored data and the event
+    sources is provided by the library libudev.</para>
+  </refsect1>
+
+  <refsect1><title>Configuration</title>
+    <para>udev configuration files are placed in <filename>/etc/udev</filename>
+    and <filename>/usr/lib/udev</filename>. All empty lines or lines beginning with
+    '#' are ignored.</para>
+
+    <refsect2><title>Configuration file</title>
+      <para>udev expects its main configuration file at <filename>/etc/udev/udev.conf</filename>.
+      It consists of a set of variables allowing the user to override default udev values.
+      The following variables can be set:</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>udev_root</option></term>
+          <listitem>
+            <para>Specifies where to place the device nodes in the filesystem.
+            The default value is <filename>/dev</filename>.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>udev_log</option></term>
+          <listitem>
+            <para>The logging priority. Valid values are the numerical syslog priorities
+            or their textual representations: <option>err</option>, <option>info</option>
+            and <option>debug</option>.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>Rules files</title>
+      <para>The udev rules are read from the files located in the
+      system rules directory <filename>/usr/lib/udev/rules.d</filename>,
+      the volatile runtime directory <filename>/run/udev/rules.d</filename>
+      and the local administration directory <filename>/etc/udev/rules.d</filename>.
+      All rules files are collectively sorted and processed in lexical order,
+      regardless of the directories in which they live. However, files with
+      identical file names replace each other. Files in <filename>/etc</filename>
+      have the highest priority, files in <filename>/run</filename> take precedence
+      over files with the same name in <filename>/lib</filename>. This can be
+      used to override a system-supplied rules file with a local file if needed;
+      a symlink in <filename>/etc</filename> with the same name as a rules file in
+      <filename>/lib</filename>, pointing to <filename>/dev/null</filename>,
+      disables the rules file entirely.</para>
+
+      <para>Rule files must have the extension <filename>.rules</filename>; other
+      extensions are ignored.</para>
+
+      <para>Every line in the rules file contains at least one key-value pair.
+      There are two kind of keys: match and assignment.
+      If all match keys are matching against its value, the rule gets applied and the
+      assignment keys get the specified value assigned.</para>
+
+      <para>A matching rule may rename a network interface, add symlinks
+      pointing to the device node, or run a specified program as part of
+      the event handling.</para>
+
+      <para>A rule consists of a comma-separated list of one or more key-value pairs.
+      Each key has a distinct operation, depending on the used operator. Valid
+      operators are:</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>==</option></term>
+          <listitem>
+            <para>Compare for equality.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>!=</option></term>
+          <listitem>
+            <para>Compare for inequality.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>=</option></term>
+          <listitem>
+            <para>Assign a value to a key. Keys that represent a list are reset
+            and only this single value is assigned.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>+=</option></term>
+          <listitem>
+            <para>Add the value to a key that holds a list of entries.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>:=</option></term>
+          <listitem>
+            <para>Assign  a  value  to  a key finally; disallow any later changes.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+
+      <para>The following key names can be used to match against device properties.
+      Some of the keys also match against properties of the parent devices in sysfs,
+      not only the device that has generated the event. If multiple keys that match
+      a parent device are specified in a single rule, all these keys must match at
+      one and the same parent device.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>ACTION</option></term>
+          <listitem>
+            <para>Match the name of the event action.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>DEVPATH</option></term>
+          <listitem>
+            <para>Match the devpath of the event device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>KERNEL</option></term>
+          <listitem>
+            <para>Match the name of the event device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>NAME</option></term>
+          <listitem>
+            <para>Match the name of a network interface. It can be used once the
+            NAME key has been set in one of the preceding rules.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>SYMLINK</option></term>
+          <listitem>
+            <para>Match the name of a symlink targeting the node. It can
+            be used once a SYMLINK key has been set in one of the preceding
+            rules. There may be multiple symlinks; only one needs to match.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>SUBSYSTEM</option></term>
+          <listitem>
+            <para>Match the subsystem of the event device.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>DRIVER</option></term>
+          <listitem>
+            <para>Match the driver name of the event device. Only set this key for devices
+            which are bound to a driver at the time the event is generated.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>ATTR{<replaceable>filename</replaceable>}</option></term>
+          <listitem>
+            <para>Match sysfs attribute values of the event device. Trailing
+            whitespace in the attribute values is ignored unless the specified match
+            value itself contains trailing whitespace.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>KERNELS</option></term>
+          <listitem>
+            <para>Search the devpath upwards for a matching device name.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>SUBSYSTEMS</option></term>
+          <listitem>
+            <para>Search the devpath upwards for a matching device subsystem name.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>DRIVERS</option></term>
+          <listitem>
+            <para>Search the devpath upwards for a matching device driver name.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>ATTRS{<replaceable>filename</replaceable>}</option></term>
+          <listitem>
+            <para>Search the devpath upwards for a device with matching sysfs attribute values.
+            If multiple <option>ATTRS</option> matches are specified, all of them
+            must match on the same device. Trailing whitespace in the attribute values is ignored
+            unless the specified match value itself contains trailing whitespace.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>TAGS</option></term>
+          <listitem>
+            <para>Search the devpath upwards for a device with matching tag.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+          <listitem>
+            <para>Match against a device property value.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>TAG</option></term>
+          <listitem>
+            <para>Match against a device tag.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>TEST{<replaceable>octal mode mask</replaceable>}</option></term>
+          <listitem>
+            <para>Test the existence of a file. An octal mode mask can be specified
+            if needed.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>PROGRAM</option></term>
+          <listitem>
+            <para>Execute a program to determine whether there
+            is a match; the key is true if the program returns
+            successfully. The device properties are made available to the
+            executed program in the environment. The program's stdout
+            is available in the RESULT key.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>RESULT</option></term>
+          <listitem>
+            <para>Match the returned string of the last PROGRAM call. This key can
+            be used in the same or in any later rule after a PROGRAM call.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+
+      <para>Most of the fields support shell-style pattern matching. The following
+      pattern characters are supported:</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>*</option></term>
+          <listitem>
+            <para>Matches zero or more characters.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>?</option></term>
+          <listitem>
+            <para>Matches any single character.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>[]</option></term>
+          <listitem>
+            <para>Matches any single character specified within the brackets. For
+            example, the pattern string 'tty[SR]' would match either 'ttyS' or 'ttyR'.
+            Ranges are also supported via the '-' character.
+            For example, to match on the range of all digits, the pattern [0-9] could
+            be used. If the first character following the '[' is a '!', any characters
+            not enclosed are matched.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+
+      <para>The following keys can get values assigned:</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>NAME</option></term>
+          <listitem>
+            <para>The name to use for a network interface. The name of a device node
+            can not be changed by udev, only additional symlinks can be created.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>SYMLINK</option></term>
+          <listitem>
+            <para>The name of a symlink targeting the node. Every matching rule adds
+            this value to the list of symlinks to be created. Multiple symlinks may be
+            specified by separating the names by the space character. In case multiple
+            devices claim the same name, the link always points to the device with
+            the highest link_priority. If the current device goes away, the links are
+            re-evaluated and the device with the next highest link_priority becomes the owner of
+            the link. If no link_priority is specified, the order of the devices (and
+            which one of them owns the link) is undefined. Also, symlink names must
+            never conflict with the kernel's default device node names, as that would
+            result in unpredictable behavior.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>OWNER, GROUP, MODE</option></term>
+          <listitem>
+            <para>The permissions for the device node. Every specified value overrides
+            the compiled-in default value.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>ATTR{<replaceable>key</replaceable>}</option></term>
+          <listitem>
+            <para>The value that should be written to a sysfs attribute of the
+            event device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+          <listitem>
+            <para>Set a device property value. Property names with a leading '.'
+            are neither stored in the database nor exported to events or
+            external tools (run by, say, the PROGRAM match key).</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>TAG</option></term>
+          <listitem>
+            <para>Attach a tag to a device. This is used to filter events for users
+            of libudev's monitor functionality, or to enumerate a group of tagged
+            devices. The implementation can only work efficiently if only a few
+            tags are attached to a device. It is only meant to be used in
+            contexts with specific device filter requirements, and not as a
+            general-purpose flag. Excessive use might result in inefficient event
+            handling.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>RUN</option></term>
+          <listitem>
+            <para>Add a program to the list of programs to be executed for a specific
+            device.</para>
+            <para>If no absolute path is given, the program is expected to live in
+            /usr/lib/udev, otherwise the absolute path must be specified. The program
+            name and following arguments are separated by spaces. Single quotes can
+            be used to specify arguments with spaces.</para>
+            <para>This can only be used for very short running tasks. Running an
+            event process for a long period of time may block all further events for
+            this or a dependent device. Starting daemons or other long running processes
+            is not appropriate for udev.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>LABEL</option></term>
+          <listitem>
+            <para>A named label to which a GOTO may jump.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>GOTO</option></term>
+          <listitem>
+            <para>Jumps to the next LABEL with a matching name.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>IMPORT{<replaceable>type</replaceable>}</option></term>
+          <listitem>
+            <para>Import a set of variables as device properties,
+            depending on <replaceable>type</replaceable>:</para>
+            <variablelist>
+              <varlistentry>
+                <term><option>program</option></term>
+                <listitem>
+                  <para>Execute an external program specified as the assigned value and
+                  import its output, which must be in environment key
+                  format. Path specification, command/argument separation,
+                  and quoting work like in <option>RUN</option>.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>file</option></term>
+                <listitem>
+                  <para>Import a text file specified as the assigned value, the content
+                  of which must be in environment key format.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>db</option></term>
+                <listitem>
+                  <para>Import a single property specified as the assigned value from the
+                  current device database. This works only if the database is already populated
+                  by an earlier event.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>cmdline</option></term>
+                <listitem>
+                  <para>Import a single property from the kernel command line. For simple flags
+                  the value of the property is set to '1'.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>parent</option></term>
+                <listitem>
+                  <para>Import the stored keys from the parent device by reading
+                  the database entry of the parent device. The value assigned to
+                  <option>IMPORT{parent}</option> is used as a filter of key names
+                  to import (with the same shell-style pattern matching used for
+                  comparisons).</para>
+                </listitem>
+              </varlistentry>
+            </variablelist>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>WAIT_FOR</option></term>
+          <listitem>
+            <para>Wait for a file to become available or until a timeout of
+            10 seconds expires. The path is relative to the sysfs device;
+            if no path is specified, this waits for an attribute to appear.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>OPTIONS</option></term>
+          <listitem>
+            <para>Rule and device options:</para>
+            <variablelist>
+              <varlistentry>
+                <term><option>link_priority=<replaceable>value</replaceable></option></term>
+                <listitem>
+                  <para>Specify the priority of the created symlinks. Devices with higher
+                  priorities overwrite existing symlinks of other devices. The default is 0.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>event_timeout=</option></term>
+                <listitem>
+                  <para>Number of seconds an event waits for operations to finish before
+                  giving up and terminating itself.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>string_escape=<replaceable>none|replace</replaceable></option></term>
+                <listitem>
+                  <para>Usually control and other possibly unsafe characters are replaced
+                  in strings used for device naming. The mode of replacement can be specified
+                  with this option.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>static_node=</option></term>
+                <listitem>
+                  <para>Apply the permissions specified in this rule to the static device node with
+                  the specified name. Static device nodes might be provided by kernel modules
+                  or copied from <filename>/usr/lib/udev/devices</filename>. These nodes might not have
+                  a corresponding kernel device at the time udevd is started; they can trigger
+                  automatic kernel module loading.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>watch</option></term>
+                <listitem>
+                  <para>Watch the device node with inotify; when the node is closed after being opened for
+                  writing, a change uevent is synthesized.</para>
+                </listitem>
+              </varlistentry>
+              <varlistentry>
+                <term><option>nowatch</option></term>
+                <listitem>
+                  <para>Disable the watching of a device node with inotify.</para>
+                </listitem>
+              </varlistentry>
+            </variablelist>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+
+      <para>The <option>NAME</option>, <option>SYMLINK</option>, <option>PROGRAM</option>,
+      <option>OWNER</option>, <option>GROUP</option>, <option>MODE</option>  and  <option>RUN</option>
+      fields support simple string substitutions. The <option>RUN</option>
+      substitutions are performed after all rules have been processed, right before the program
+      is executed, allowing for the use of device properties set by earlier matching
+      rules. For all other fields, substitutions are performed while the individual rule is
+      being processed. The available substitutions are:</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>$kernel</option>, <option>%k</option></term>
+          <listitem>
+            <para>The kernel name for this device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$number</option>, <option>%n</option></term>
+          <listitem>
+            <para>The kernel number for this device. For example, 'sda3' has
+            kernel number of '3'</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$devpath</option>, <option>%p</option></term>
+          <listitem>
+            <para>The devpath of the device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$id</option>, <option>%b</option></term>
+          <listitem>
+            <para>The name of the device matched while searching the devpath upwards for
+              <option>SUBSYSTEMS</option>, <option>KERNELS</option>, <option>DRIVERS</option> and <option>ATTRS</option>.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$driver</option></term>
+          <listitem>
+            <para>The driver name of the device matched while searching the devpath upwards for
+              <option>SUBSYSTEMS</option>, <option>KERNELS</option>, <option>DRIVERS</option> and <option>ATTRS</option>.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$attr{<replaceable>file</replaceable>}</option>, <option>%s{<replaceable>file</replaceable>}</option></term>
+          <listitem>
+            <para>The value of a sysfs attribute found at the device where
+            all keys of the rule have matched. If the matching device does not have
+            such an attribute, and a previous KERNELS, SUBSYSTEMS, DRIVERS, or
+            ATTRS test selected a parent device, then the attribute from that
+            parent device is used.</para>
+            <para>If the attribute is a symlink, the last element of the symlink target is
+            returned as the value.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$env{<replaceable>key</replaceable>}</option>, <option>%E{<replaceable>key</replaceable>}</option></term>
+          <listitem>
+            <para>A device property value.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$major</option>, <option>%M</option></term>
+          <listitem>
+            <para>The kernel major number for the device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$minor</option>, <option>%m</option></term>
+          <listitem>
+            <para>The kernel minor number for the device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$result</option>, <option>%c</option></term>
+          <listitem>
+            <para>The string returned by the external program requested with PROGRAM.
+            A single part of the string, separated by a space character, may be selected
+            by specifying the part number as an attribute: <option>%c{N}</option>.
+            If the number is followed by the '+' character, this part plus all remaining parts
+            of the result string are substituted: <option>%c{N+}</option></para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$parent</option>, <option>%P</option></term>
+          <listitem>
+            <para>The node name of the parent device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$name</option></term>
+          <listitem>
+            <para>The current name of the device. If not changed by a rule, it is the
+            name of the kernel device.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$links</option></term>
+          <listitem>
+            <para>A space-separated list of the current symlinks. The value is
+            only set during a remove event or if an earlier rule assigned a value.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$root</option>, <option>%r</option></term>
+          <listitem>
+            <para>The udev_root value.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$sys</option>, <option>%S</option></term>
+          <listitem>
+            <para>The sysfs mount point.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$devnode</option>, <option>%N</option></term>
+          <listitem>
+            <para>The name of the device node.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>%%</option></term>
+          <listitem>
+          <para>The '%' character itself.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><option>$$</option></term>
+          <listitem>
+          <para>The '$' character itself.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+  </refsect1>
+
+  <refsect1><title>Author</title>
+    <para>Written by Greg Kroah-Hartman <email>greg@kroah.com</email> and
+    Kay Sievers <email>kay.sievers@vrfy.org</email>. With much help from
+    Dan Stekloff and many others.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry>
+        <refentrytitle>udevd</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum>
+    </citerefentry></para>
+  </refsect1>
+</refentry>
diff --git a/src/udevadm-control.c b/src/udevadm-control.c
new file mode 100644
index 0000000..cafa214
--- /dev/null
+++ b/src/udevadm-control.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2005-2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+
+#include "udev.h"
+
+static void print_help(void)
+{
+        printf("Usage: udevadm control COMMAND\n"
+                "  --exit                   instruct the daemon to cleanup and exit\n"
+                "  --log-priority=<level>   set the udev log level for the daemon\n"
+                "  --stop-exec-queue        do not execute events, queue only\n"
+                "  --start-exec-queue       execute events, flush queue\n"
+                "  --reload                 reload rules and databases\n"
+                "  --property=<KEY>=<value> set a global property for all events\n"
+                "  --children-max=<N>       maximum number of children\n"
+                "  --timeout=<seconds>      maximum time to block for a reply\n"
+                "  --help                   print this help text\n\n");
+}
+
+static int adm_control(struct udev *udev, int argc, char *argv[])
+{
+        struct udev_ctrl *uctrl = NULL;
+        int timeout = 60;
+        int rc = 1;
+
+        static const struct option options[] = {
+                { "exit", no_argument, NULL, 'e' },
+                { "log-priority", required_argument, NULL, 'l' },
+                { "stop-exec-queue", no_argument, NULL, 's' },
+                { "start-exec-queue", no_argument, NULL, 'S' },
+                { "reload", no_argument, NULL, 'R' },
+                { "reload-rules", no_argument, NULL, 'R' },
+                { "property", required_argument, NULL, 'p' },
+                { "env", required_argument, NULL, 'p' },
+                { "children-max", required_argument, NULL, 'm' },
+                { "timeout", required_argument, NULL, 't' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+
+        if (getuid() != 0) {
+                fprintf(stderr, "root privileges required\n");
+                return 1;
+        }
+
+        uctrl = udev_ctrl_new(udev);
+        if (uctrl == NULL)
+                return 2;
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "el:sSRp:m:h", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'e':
+                        if (udev_ctrl_send_exit(uctrl, timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                case 'l': {
+                        int i;
+
+                        i = util_log_priority(optarg);
+                        if (i < 0) {
+                                fprintf(stderr, "invalid number '%s'\n", optarg);
+                                goto out;
+                        }
+                        if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg), timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                }
+                case 's':
+                        if (udev_ctrl_send_stop_exec_queue(uctrl, timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                case 'S':
+                        if (udev_ctrl_send_start_exec_queue(uctrl, timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                case 'R':
+                        if (udev_ctrl_send_reload(uctrl, timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                case 'p':
+                        if (strchr(optarg, '=') == NULL) {
+                                fprintf(stderr, "expect <KEY>=<value> instead of '%s'\n", optarg);
+                                goto out;
+                        }
+                        if (udev_ctrl_send_set_env(uctrl, optarg, timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                case 'm': {
+                        char *endp;
+                        int i;
+
+                        i = strtoul(optarg, &endp, 0);
+                        if (endp[0] != '\0' || i < 1) {
+                                fprintf(stderr, "invalid number '%s'\n", optarg);
+                                goto out;
+                        }
+                        if (udev_ctrl_send_set_children_max(uctrl, i, timeout) < 0)
+                                rc = 2;
+                        else
+                                rc = 0;
+                        break;
+                }
+                case 't': {
+                        int seconds;
+
+                        seconds = atoi(optarg);
+                        if (seconds >= 0)
+                                timeout = seconds;
+                        else
+                                fprintf(stderr, "invalid timeout value\n");
+                        break;
+                }
+                case 'h':
+                        print_help();
+                        rc = 0;
+                        break;
+                }
+        }
+
+        if (argv[optind] != NULL)
+                fprintf(stderr, "unknown option\n");
+        else if (optind == 1)
+                fprintf(stderr, "missing option\n");
+out:
+        udev_ctrl_unref(uctrl);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_control = {
+        .name = "control",
+        .cmd = adm_control,
+        .help = "control the udev daemon",
+};
diff --git a/src/udevadm-info.c b/src/udevadm-info.c
new file mode 100644
index 0000000..ee9b59f
--- /dev/null
+++ b/src/udevadm-info.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2004-2009 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+static bool skip_attribute(const char *name)
+{
+        static const char const *skip[] = {
+                "uevent",
+                "dev",
+                "modalias",
+                "resource",
+                "driver",
+                "subsystem",
+                "module",
+        };
+        unsigned int i;
+
+        for (i = 0; i < ARRAY_SIZE(skip); i++)
+                if (strcmp(name, skip[i]) == 0)
+                        return true;
+        return false;
+}
+
+static void print_all_attributes(struct udev_device *device, const char *key)
+{
+        struct udev *udev = udev_device_get_udev(device);
+        struct udev_list_entry *sysattr;
+
+        udev_list_entry_foreach(sysattr, udev_device_get_sysattr_list_entry(device)) {
+                const char *name;
+                const char *value;
+                size_t len;
+
+                name = udev_list_entry_get_name(sysattr);
+                if (skip_attribute(name))
+                        continue;
+
+                value = udev_device_get_sysattr_value(device, name);
+                if (value == NULL)
+                        continue;
+                dbg(udev, "attr '%s'='%s'\n", name, value);
+
+                /* skip any values that look like a path */
+                if (value[0] == '/')
+                        continue;
+
+                /* skip nonprintable attributes */
+                len = strlen(value);
+                while (len > 0 && isprint(value[len-1]))
+                        len--;
+                if (len > 0) {
+                        dbg(udev, "attribute value of '%s' non-printable, skip\n", name);
+                        continue;
+                }
+
+                printf("    %s{%s}==\"%s\"\n", key, name, value);
+        }
+        printf("\n");
+}
+
+static int print_device_chain(struct udev_device *device)
+{
+        struct udev_device *device_parent;
+        const char *str;
+
+        printf("\n"
+               "Udevadm info starts with the device specified by the devpath and then\n"
+               "walks up the chain of parent devices. It prints for every device\n"
+               "found, all possible attributes in the udev rules key format.\n"
+               "A rule to match, can be composed by the attributes of the device\n"
+               "and the attributes from one single parent device.\n"
+               "\n");
+
+        printf("  looking at device '%s':\n", udev_device_get_devpath(device));
+        printf("    KERNEL==\"%s\"\n", udev_device_get_sysname(device));
+        str = udev_device_get_subsystem(device);
+        if (str == NULL)
+                str = "";
+        printf("    SUBSYSTEM==\"%s\"\n", str);
+        str = udev_device_get_driver(device);
+        if (str == NULL)
+                str = "";
+        printf("    DRIVER==\"%s\"\n", str);
+        print_all_attributes(device, "ATTR");
+
+        device_parent = device;
+        do {
+                device_parent = udev_device_get_parent(device_parent);
+                if (device_parent == NULL)
+                        break;
+                printf("  looking at parent device '%s':\n", udev_device_get_devpath(device_parent));
+                printf("    KERNELS==\"%s\"\n", udev_device_get_sysname(device_parent));
+                str = udev_device_get_subsystem(device_parent);
+                if (str == NULL)
+                        str = "";
+                printf("    SUBSYSTEMS==\"%s\"\n", str);
+                str = udev_device_get_driver(device_parent);
+                if (str == NULL)
+                        str = "";
+                printf("    DRIVERS==\"%s\"\n", str);
+                print_all_attributes(device_parent, "ATTRS");
+        } while (device_parent != NULL);
+
+        return 0;
+}
+
+static void print_record(struct udev_device *device)
+{
+        size_t len;
+        const char *str;
+        int i;
+        struct udev_list_entry *list_entry;
+
+        printf("P: %s\n", udev_device_get_devpath(device));
+
+        len = strlen(udev_get_dev_path(udev_device_get_udev(device)));
+        str = udev_device_get_devnode(device);
+        if (str != NULL)
+                printf("N: %s\n", &str[len+1]);
+
+        i = udev_device_get_devlink_priority(device);
+        if (i != 0)
+                printf("L: %i\n", i);
+
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device)) {
+                len = strlen(udev_get_dev_path(udev_device_get_udev(device)));
+                printf("S: %s\n", &udev_list_entry_get_name(list_entry)[len+1]);
+        }
+
+        udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
+                printf("E: %s=%s\n",
+                       udev_list_entry_get_name(list_entry),
+                       udev_list_entry_get_value(list_entry));
+        printf("\n");
+}
+
+static int stat_device(const char *name, bool export, const char *prefix)
+{
+        struct stat statbuf;
+
+        if (stat(name, &statbuf) != 0)
+                return -1;
+
+        if (export) {
+                if (prefix == NULL)
+                        prefix = "INFO_";
+                printf("%sMAJOR=%d\n"
+                       "%sMINOR=%d\n",
+                       prefix, major(statbuf.st_dev),
+                       prefix, minor(statbuf.st_dev));
+        } else
+                printf("%d:%d\n", major(statbuf.st_dev), minor(statbuf.st_dev));
+        return 0;
+}
+
+static int export_devices(struct udev *udev)
+{
+        struct udev_enumerate *udev_enumerate;
+        struct udev_list_entry *list_entry;
+
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_scan_devices(udev_enumerate);
+        udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(udev_enumerate)) {
+                struct udev_device *device;
+
+                device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(list_entry));
+                if (device != NULL) {
+                        print_record(device);
+                        udev_device_unref(device);
+                }
+        }
+        udev_enumerate_unref(udev_enumerate);
+        return 0;
+}
+
+static void cleanup_dir(DIR *dir, mode_t mask, int depth)
+{
+        struct dirent *dent;
+
+        if (depth <= 0)
+                return;
+
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                struct stat stats;
+
+                if (dent->d_name[0] == '.')
+                        continue;
+                if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
+                        continue;
+                if ((stats.st_mode & mask) != 0)
+                        continue;
+                if (S_ISDIR(stats.st_mode)) {
+                        DIR *dir2;
+
+                        dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+                        if (dir2 != NULL) {
+                                cleanup_dir(dir2, mask, depth-1);
+                                closedir(dir2);
+                        }
+                        unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
+                } else {
+                        unlinkat(dirfd(dir), dent->d_name, 0);
+                }
+        }
+}
+
+static void cleanup_db(struct udev *udev)
+{
+        char filename[UTIL_PATH_SIZE];
+        DIR *dir;
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/queue.bin", NULL);
+        unlink(filename);
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/data", NULL);
+        dir = opendir(filename);
+        if (dir != NULL) {
+                cleanup_dir(dir, S_ISVTX, 1);
+                closedir(dir);
+        }
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/links", NULL);
+        dir = opendir(filename);
+        if (dir != NULL) {
+                cleanup_dir(dir, 0, 2);
+                closedir(dir);
+        }
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/tags", NULL);
+        dir = opendir(filename);
+        if (dir != NULL) {
+                cleanup_dir(dir, 0, 2);
+                closedir(dir);
+        }
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/watch", NULL);
+        dir = opendir(filename);
+        if (dir != NULL) {
+                cleanup_dir(dir, 0, 1);
+                closedir(dir);
+        }
+
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/firmware-missing", NULL);
+        dir = opendir(filename);
+        if (dir != NULL) {
+                cleanup_dir(dir, 0, 1);
+                closedir(dir);
+        }
+}
+
+static int uinfo(struct udev *udev, int argc, char *argv[])
+{
+        struct udev_device *device = NULL;
+        bool root = 0;
+        bool export = 0;
+        const char *export_prefix = NULL;
+        char path[UTIL_PATH_SIZE];
+        char name[UTIL_PATH_SIZE];
+        struct udev_list_entry *list_entry;
+        int rc = 0;
+
+        static const struct option options[] = {
+                { "name", required_argument, NULL, 'n' },
+                { "path", required_argument, NULL, 'p' },
+                { "query", required_argument, NULL, 'q' },
+                { "attribute-walk", no_argument, NULL, 'a' },
+                { "cleanup-db", no_argument, NULL, 'c' },
+                { "export-db", no_argument, NULL, 'e' },
+                { "root", no_argument, NULL, 'r' },
+                { "run", no_argument, NULL, 'R' },
+                { "device-id-of-file", required_argument, NULL, 'd' },
+                { "export", no_argument, NULL, 'x' },
+                { "export-prefix", required_argument, NULL, 'P' },
+                { "version", no_argument, NULL, 'V' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+
+        enum action_type {
+                ACTION_NONE,
+                ACTION_QUERY,
+                ACTION_ATTRIBUTE_WALK,
+                ACTION_ROOT,
+                ACTION_DEVICE_ID_FILE,
+        } action = ACTION_NONE;
+
+        enum query_type {
+                QUERY_NONE,
+                QUERY_NAME,
+                QUERY_PATH,
+                QUERY_SYMLINK,
+                QUERY_PROPERTY,
+                QUERY_ALL,
+        } query = QUERY_NONE;
+
+        for (;;) {
+                int option;
+                struct stat statbuf;
+
+                option = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL);
+                if (option == -1)
+                        break;
+
+                dbg(udev, "option '%c'\n", option);
+                switch (option) {
+                case 'n':
+                        if (device != NULL) {
+                                fprintf(stderr, "device already specified\n");
+                                rc = 2;
+                                goto exit;
+                        }
+                        /* remove /dev if given */
+                        if (strncmp(optarg, udev_get_dev_path(udev), strlen(udev_get_dev_path(udev))) != 0)
+                                util_strscpyl(name, sizeof(name), udev_get_dev_path(udev), "/", optarg, NULL);
+                        else
+                                util_strscpy(name, sizeof(name), optarg);
+                        util_remove_trailing_chars(name, '/');
+                        if (stat(name, &statbuf) < 0) {
+                                fprintf(stderr, "device node not found\n");
+                                rc = 2;
+                                goto exit;
+                        } else {
+                                char type;
+
+                                if (S_ISBLK(statbuf.st_mode)) {
+                                        type = 'b';
+                                } else if (S_ISCHR(statbuf.st_mode)) {
+                                        type = 'c';
+                                } else {
+                                        fprintf(stderr, "device node has wrong file type\n");
+                                        rc = 2;
+                                        goto exit;
+                                }
+                                device = udev_device_new_from_devnum(udev, type, statbuf.st_rdev);
+                                if (device == NULL) {
+                                        fprintf(stderr, "device node not found\n");
+                                        rc = 2;
+                                        goto exit;
+                                }
+                        }
+                        break;
+                case 'p':
+                        if (device != NULL) {
+                                fprintf(stderr, "device already specified\n");
+                                rc = 2;
+                                goto exit;
+                        }
+                        /* add sys dir if needed */
+                        if (strncmp(optarg, udev_get_sys_path(udev), strlen(udev_get_sys_path(udev))) != 0)
+                                util_strscpyl(path, sizeof(path), udev_get_sys_path(udev), optarg, NULL);
+                        else
+                                util_strscpy(path, sizeof(path), optarg);
+                        util_remove_trailing_chars(path, '/');
+                        device = udev_device_new_from_syspath(udev, path);
+                        if (device == NULL) {
+                                fprintf(stderr, "device path not found\n");
+                                rc = 2;
+                                goto exit;
+                        }
+                        break;
+                case 'q':
+                        action = ACTION_QUERY;
+                        if (strcmp(optarg, "property") == 0 || strcmp(optarg, "env") == 0) {
+                                query = QUERY_PROPERTY;
+                        } else if (strcmp(optarg, "name") == 0) {
+                                query = QUERY_NAME;
+                        } else if (strcmp(optarg, "symlink") == 0) {
+                                query = QUERY_SYMLINK;
+                        } else if (strcmp(optarg, "path") == 0) {
+                                query = QUERY_PATH;
+                        } else if (strcmp(optarg, "all") == 0) {
+                                query = QUERY_ALL;
+                        } else {
+                                fprintf(stderr, "unknown query type\n");
+                                rc = 3;
+                                goto exit;
+                        }
+                        break;
+                case 'r':
+                        if (action == ACTION_NONE)
+                                action = ACTION_ROOT;
+                        root = true;
+                        break;
+                case 'R':
+                        printf("%s\n", udev_get_run_path(udev));
+                        goto exit;
+                case 'd':
+                        action = ACTION_DEVICE_ID_FILE;
+                        util_strscpy(name, sizeof(name), optarg);
+                        break;
+                case 'a':
+                        action = ACTION_ATTRIBUTE_WALK;
+                        break;
+                case 'e':
+                        export_devices(udev);
+                        goto exit;
+                case 'c':
+                        cleanup_db(udev);
+                        goto exit;
+                case 'x':
+                        export = true;
+                        break;
+                case 'P':
+                        export_prefix = optarg;
+                        break;
+                case 'V':
+                        printf("%s\n", VERSION);
+                        goto exit;
+                case 'h':
+                        printf("Usage: udevadm info OPTIONS\n"
+                               "  --query=<type>             query device information:\n"
+                               "      name                     name of device node\n"
+                               "      symlink                  pointing to node\n"
+                               "      path                     sys device path\n"
+                               "      property                 the device properties\n"
+                               "      all                      all values\n"
+                               "  --path=<syspath>           sys device path used for query or attribute walk\n"
+                               "  --name=<name>              node or symlink name used for query or attribute walk\n"
+                               "  --root                     prepend dev directory to path names\n"
+                               "  --attribute-walk           print all key matches while walking along the chain\n"
+                               "                             of parent devices\n"
+                               "  --device-id-of-file=<file> print major:minor of device containing this file\n"
+                               "  --export                   export key/value pairs\n"
+                               "  --export-prefix            export the key name with a prefix\n"
+                               "  --export-db                export the content of the udev database\n"
+                               "  --cleanup-db               cleanup the udev database\n"
+                               "  --help\n\n");
+                        goto exit;
+                default:
+                        rc = 1;
+                        goto exit;
+                }
+        }
+
+        switch (action) {
+        case ACTION_QUERY:
+                if (device == NULL) {
+                        fprintf(stderr, "query needs a valid device specified by --path= or --name=\n");
+                        rc = 4;
+                        goto exit;
+                }
+
+                switch(query) {
+                case QUERY_NAME: {
+                        const char *node = udev_device_get_devnode(device);
+
+                        if (node == NULL) {
+                                fprintf(stderr, "no device node found\n");
+                                rc = 5;
+                                goto exit;
+                        }
+
+                        if (root) {
+                                printf("%s\n", udev_device_get_devnode(device));
+                        } else {
+                                size_t len = strlen(udev_get_dev_path(udev));
+
+                                printf("%s\n", &udev_device_get_devnode(device)[len+1]);
+                        }
+                        break;
+                }
+                case QUERY_SYMLINK:
+                        list_entry = udev_device_get_devlinks_list_entry(device);
+                        while (list_entry != NULL) {
+                                if (root) {
+                                        printf("%s", udev_list_entry_get_name(list_entry));
+                                } else {
+                                        size_t len;
+
+                                        len = strlen(udev_get_dev_path(udev_device_get_udev(device)));
+                                        printf("%s", &udev_list_entry_get_name(list_entry)[len+1]);
+                                }
+                                list_entry = udev_list_entry_get_next(list_entry);
+                                if (list_entry != NULL)
+                                        printf(" ");
+                        }
+                        printf("\n");
+                        break;
+                case QUERY_PATH:
+                        printf("%s\n", udev_device_get_devpath(device));
+                        goto exit;
+                case QUERY_PROPERTY:
+                        list_entry = udev_device_get_properties_list_entry(device);
+                        while (list_entry != NULL) {
+                                if (export) {
+                                        const char *prefix = export_prefix;
+
+                                        if (prefix == NULL)
+                                                prefix = "";
+                                        printf("%s%s='%s'\n", prefix,
+                                               udev_list_entry_get_name(list_entry),
+                                               udev_list_entry_get_value(list_entry));
+                                } else {
+                                        printf("%s=%s\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry));
+                                }
+                                list_entry = udev_list_entry_get_next(list_entry);
+                        }
+                        break;
+                case QUERY_ALL:
+                        print_record(device);
+                        break;
+                default:
+                        fprintf(stderr, "unknown query type\n");
+                        break;
+                }
+                break;
+        case ACTION_ATTRIBUTE_WALK:
+                if (device == NULL) {
+                        fprintf(stderr, "query needs a valid device specified by --path= or --name=\n");
+                        rc = 4;
+                        goto exit;
+                }
+                print_device_chain(device);
+                break;
+        case ACTION_DEVICE_ID_FILE:
+                if (stat_device(name, export, export_prefix) != 0)
+                        rc = 1;
+                break;
+        case ACTION_ROOT:
+                printf("%s\n", udev_get_dev_path(udev));
+                break;
+        default:
+                fprintf(stderr, "missing option\n");
+                rc = 1;
+                break;
+        }
+
+exit:
+        udev_device_unref(device);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_info = {
+        .name = "info",
+        .cmd = uinfo,
+        .help = "query sysfs or the udev database",
+};
diff --git a/src/udevadm-monitor.c b/src/udevadm-monitor.c
new file mode 100644
index 0000000..5997dd8
--- /dev/null
+++ b/src/udevadm-monitor.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2004-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/epoll.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#include "udev.h"
+
+static bool udev_exit;
+
+static void sig_handler(int signum)
+{
+        if (signum == SIGINT || signum == SIGTERM)
+                udev_exit = true;
+}
+
+static void print_device(struct udev_device *device, const char *source, int prop)
+{
+        struct timespec ts;
+
+        clock_gettime(CLOCK_MONOTONIC, &ts);
+        printf("%-6s[%llu.%06u] %-8s %s (%s)\n",
+               source,
+               (unsigned long long) ts.tv_sec, (unsigned int) ts.tv_nsec/1000,
+               udev_device_get_action(device),
+               udev_device_get_devpath(device),
+               udev_device_get_subsystem(device));
+        if (prop) {
+                struct udev_list_entry *list_entry;
+
+                udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
+                        printf("%s=%s\n",
+                               udev_list_entry_get_name(list_entry),
+                               udev_list_entry_get_value(list_entry));
+                printf("\n");
+        }
+}
+
+static int adm_monitor(struct udev *udev, int argc, char *argv[])
+{
+        struct sigaction act;
+        sigset_t mask;
+        int option;
+        bool prop = false;
+        bool print_kernel = false;
+        bool print_udev = false;
+        struct udev_list subsystem_match_list;
+        struct udev_list tag_match_list;
+        struct udev_monitor *udev_monitor = NULL;
+        struct udev_monitor *kernel_monitor = NULL;
+        int fd_ep = -1;
+        int fd_kernel = -1, fd_udev = -1;
+        struct epoll_event ep_kernel, ep_udev;
+        int rc = 0;
+
+        static const struct option options[] = {
+                { "property", no_argument, NULL, 'p' },
+                { "environment", no_argument, NULL, 'e' },
+                { "kernel", no_argument, NULL, 'k' },
+                { "udev", no_argument, NULL, 'u' },
+                { "subsystem-match", required_argument, NULL, 's' },
+                { "tag-match", required_argument, NULL, 't' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+
+        udev_list_init(udev, &subsystem_match_list, true);
+        udev_list_init(udev, &tag_match_list, true);
+
+        for (;;) {
+                option = getopt_long(argc, argv, "pekus:t:h", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'p':
+                case 'e':
+                        prop = true;
+                        break;
+                case 'k':
+                        print_kernel = true;
+                        break;
+                case 'u':
+                        print_udev = true;
+                        break;
+                case 's':
+                        {
+                                char subsys[UTIL_NAME_SIZE];
+                                char *devtype;
+
+                                util_strscpy(subsys, sizeof(subsys), optarg);
+                                devtype = strchr(subsys, '/');
+                                if (devtype != NULL) {
+                                        devtype[0] = '\0';
+                                        devtype++;
+                                }
+                                udev_list_entry_add(&subsystem_match_list, subsys, devtype);
+                                break;
+                        }
+                case 't':
+                        udev_list_entry_add(&tag_match_list, optarg, NULL);
+                        break;
+                case 'h':
+                        printf("Usage: udevadm monitor [--property] [--kernel] [--udev] [--help]\n"
+                               "  --property                              print the event properties\n"
+                               "  --kernel                                print kernel uevents\n"
+                               "  --udev                                  print udev events\n"
+                               "  --subsystem-match=<subsystem[/devtype]> filter events by subsystem\n"
+                               "  --tag-match=<tag>                       filter events by tag\n"
+                               "  --help\n\n");
+                        goto out;
+                default:
+                        rc = 1;
+                        goto out;
+                }
+        }
+
+        if (!print_kernel && !print_udev) {
+                print_kernel = true;
+                print_udev = true;
+        }
+
+        /* set signal handlers */
+        memset(&act, 0x00, sizeof(struct sigaction));
+        act.sa_handler = sig_handler;
+        sigemptyset(&act.sa_mask);
+        act.sa_flags = SA_RESTART;
+        sigaction(SIGINT, &act, NULL);
+        sigaction(SIGTERM, &act, NULL);
+        sigemptyset(&mask);
+        sigaddset(&mask, SIGINT);
+        sigaddset(&mask, SIGTERM);
+        sigprocmask(SIG_UNBLOCK, &mask, NULL);
+
+        fd_ep = epoll_create1(EPOLL_CLOEXEC);
+        if (fd_ep < 0) {
+                err(udev, "error creating epoll fd: %m\n");
+                goto out;
+        }
+
+        printf("monitor will print the received events for:\n");
+        if (print_udev) {
+                struct udev_list_entry *entry;
+
+                udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
+                if (udev_monitor == NULL) {
+                        fprintf(stderr, "error: unable to create netlink socket\n");
+                        rc = 1;
+                        goto out;
+                }
+                udev_monitor_set_receive_buffer_size(udev_monitor, 128*1024*1024);
+                fd_udev = udev_monitor_get_fd(udev_monitor);
+
+                udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) {
+                        const char *subsys = udev_list_entry_get_name(entry);
+                        const char *devtype = udev_list_entry_get_value(entry);
+
+                        if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, subsys, devtype) < 0)
+                                fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys);
+                }
+
+                udev_list_entry_foreach(entry, udev_list_get_entry(&tag_match_list)) {
+                        const char *tag = udev_list_entry_get_name(entry);
+
+                        if (udev_monitor_filter_add_match_tag(udev_monitor, tag) < 0)
+                                fprintf(stderr, "error: unable to apply tag filter '%s'\n", tag);
+                }
+
+                if (udev_monitor_enable_receiving(udev_monitor) < 0) {
+                        fprintf(stderr, "error: unable to subscribe to udev events\n");
+                        rc = 2;
+                        goto out;
+                }
+
+                memset(&ep_udev, 0, sizeof(struct epoll_event));
+                ep_udev.events = EPOLLIN;
+                ep_udev.data.fd = fd_udev;
+                if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) {
+                        err(udev, "fail to add fd to epoll: %m\n");
+                        goto out;
+                }
+
+                printf("UDEV - the event which udev sends out after rule processing\n");
+        }
+
+        if (print_kernel) {
+                struct udev_list_entry *entry;
+
+                kernel_monitor = udev_monitor_new_from_netlink(udev, "kernel");
+                if (kernel_monitor == NULL) {
+                        fprintf(stderr, "error: unable to create netlink socket\n");
+                        rc = 3;
+                        goto out;
+                }
+                udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024);
+                fd_kernel = udev_monitor_get_fd(kernel_monitor);
+
+                udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) {
+                        const char *subsys = udev_list_entry_get_name(entry);
+
+                        if (udev_monitor_filter_add_match_subsystem_devtype(kernel_monitor, subsys, NULL) < 0)
+                                fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys);
+                }
+
+                if (udev_monitor_enable_receiving(kernel_monitor) < 0) {
+                        fprintf(stderr, "error: unable to subscribe to kernel events\n");
+                        rc = 4;
+                        goto out;
+                }
+
+                memset(&ep_kernel, 0, sizeof(struct epoll_event));
+                ep_kernel.events = EPOLLIN;
+                ep_kernel.data.fd = fd_kernel;
+                if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_kernel, &ep_kernel) < 0) {
+                        err(udev, "fail to add fd to epoll: %m\n");
+                        goto out;
+                }
+
+                printf("KERNEL - the kernel uevent\n");
+        }
+        printf("\n");
+
+        while (!udev_exit) {
+                int fdcount;
+                struct epoll_event ev[4];
+                int i;
+
+                fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), -1);
+                if (fdcount < 0) {
+                        if (errno != EINTR)
+                                fprintf(stderr, "error receiving uevent message: %m\n");
+                        continue;
+                }
+
+                for (i = 0; i < fdcount; i++) {
+                        if (ev[i].data.fd == fd_kernel && ev[i].events & EPOLLIN) {
+                                struct udev_device *device;
+
+                                device = udev_monitor_receive_device(kernel_monitor);
+                                if (device == NULL)
+                                        continue;
+                                print_device(device, "KERNEL", prop);
+                                udev_device_unref(device);
+                        } else if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) {
+                                struct udev_device *device;
+
+                                device = udev_monitor_receive_device(udev_monitor);
+                                if (device == NULL)
+                                        continue;
+                                print_device(device, "UDEV", prop);
+                                udev_device_unref(device);
+                        }
+                }
+        }
+out:
+        if (fd_ep >= 0)
+                close(fd_ep);
+        udev_monitor_unref(udev_monitor);
+        udev_monitor_unref(kernel_monitor);
+        udev_list_cleanup(&subsystem_match_list);
+        udev_list_cleanup(&tag_match_list);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_monitor = {
+        .name = "monitor",
+        .cmd = adm_monitor,
+        .help = "listen to kernel and udev events",
+};
diff --git a/src/udevadm-settle.c b/src/udevadm-settle.c
new file mode 100644
index 0000000..b168def
--- /dev/null
+++ b/src/udevadm-settle.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2006-2009 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+static int adm_settle(struct udev *udev, int argc, char *argv[])
+{
+        static const struct option options[] = {
+                { "seq-start", required_argument, NULL, 's' },
+                { "seq-end", required_argument, NULL, 'e' },
+                { "timeout", required_argument, NULL, 't' },
+                { "exit-if-exists", required_argument, NULL, 'E' },
+                { "quiet", no_argument, NULL, 'q' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+        unsigned long long start_usec = now_usec();
+        unsigned long long start = 0;
+        unsigned long long end = 0;
+        int quiet = 0;
+        const char *exists = NULL;
+        unsigned int timeout = 120;
+        struct pollfd pfd[1];
+        struct udev_queue *udev_queue = NULL;
+        int rc = EXIT_FAILURE;
+
+        dbg(udev, "version %s\n", VERSION);
+
+        for (;;) {
+                int option;
+                int seconds;
+
+                option = getopt_long(argc, argv, "s:e:t:E:qh", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 's':
+                        start = strtoull(optarg, NULL, 0);
+                        break;
+                case 'e':
+                        end = strtoull(optarg, NULL, 0);
+                        break;
+                case 't':
+                        seconds = atoi(optarg);
+                        if (seconds >= 0)
+                                timeout = seconds;
+                        else
+                                fprintf(stderr, "invalid timeout value\n");
+                        dbg(udev, "timeout=%i\n", timeout);
+                        break;
+                case 'q':
+                        quiet = 1;
+                        break;
+                case 'E':
+                        exists = optarg;
+                        break;
+                case 'h':
+                        printf("Usage: udevadm settle OPTIONS\n"
+                               "  --timeout=<seconds>     maximum time to wait for events\n"
+                               "  --seq-start=<seqnum>    first seqnum to wait for\n"
+                               "  --seq-end=<seqnum>      last seqnum to wait for\n"
+                               "  --exit-if-exists=<file> stop waiting if file exists\n"
+                               "  --quiet                 do not print list after timeout\n"
+                               "  --help\n\n");
+                        exit(EXIT_SUCCESS);
+                default:
+                        exit(EXIT_FAILURE);
+                }
+        }
+
+        udev_queue = udev_queue_new(udev);
+        if (udev_queue == NULL)
+                exit(2);
+
+        if (start > 0) {
+                unsigned long long kernel_seq;
+
+                kernel_seq = udev_queue_get_kernel_seqnum(udev_queue);
+
+                /* unless specified, the last event is the current kernel seqnum */
+                if (end == 0)
+                        end = udev_queue_get_kernel_seqnum(udev_queue);
+
+                if (start > end) {
+                        err(udev, "seq-start larger than seq-end, ignoring\n");
+                        start = 0;
+                        end = 0;
+                }
+
+                if (start > kernel_seq || end > kernel_seq) {
+                        err(udev, "seq-start or seq-end larger than current kernel value, ignoring\n");
+                        start = 0;
+                        end = 0;
+                }
+                info(udev, "start=%llu end=%llu current=%llu\n", start, end, kernel_seq);
+        } else {
+                if (end > 0) {
+                        err(udev, "seq-end needs seq-start parameter, ignoring\n");
+                        end = 0;
+                }
+        }
+
+        /* guarantee that the udev daemon isn't pre-processing */
+        if (getuid() == 0) {
+                struct udev_ctrl *uctrl;
+
+                uctrl = udev_ctrl_new(udev);
+                if (uctrl != NULL) {
+                        if (udev_ctrl_send_ping(uctrl, timeout) < 0) {
+                                info(udev, "no connection to daemon\n");
+                                udev_ctrl_unref(uctrl);
+                                rc = EXIT_SUCCESS;
+                                goto out;
+                        }
+                        udev_ctrl_unref(uctrl);
+                }
+        }
+
+        pfd[0].events = POLLIN;
+        pfd[0].fd = inotify_init1(IN_CLOEXEC);
+        if (pfd[0].fd < 0) {
+                err(udev, "inotify_init failed: %m\n");
+        } else {
+                if (inotify_add_watch(pfd[0].fd, udev_get_run_path(udev), IN_MOVED_TO) < 0) {
+                        err(udev, "watching '%s' failed\n", udev_get_run_path(udev));
+                        close(pfd[0].fd);
+                        pfd[0].fd = -1;
+                }
+        }
+
+        for (;;) {
+                struct stat statbuf;
+
+                if (exists != NULL && stat(exists, &statbuf) == 0) {
+                        rc = EXIT_SUCCESS;
+                        break;
+                }
+
+                if (start > 0) {
+                        /* if asked for, wait for a specific sequence of events */
+                        if (udev_queue_get_seqnum_sequence_is_finished(udev_queue, start, end) == 1) {
+                                rc = EXIT_SUCCESS;
+                                break;
+                        }
+                } else {
+                        /* exit if queue is empty */
+                        if (udev_queue_get_queue_is_empty(udev_queue)) {
+                                rc = EXIT_SUCCESS;
+                                break;
+                        }
+                }
+
+                if (pfd[0].fd >= 0) {
+                        int delay;
+
+                        if (exists != NULL || start > 0)
+                                delay = 100;
+                        else
+                                delay = 1000;
+                        /* wake up after delay, or immediately after the queue is rebuilt */
+                        if (poll(pfd, 1, delay) > 0 && pfd[0].revents & POLLIN) {
+                                char buf[sizeof(struct inotify_event) + PATH_MAX];
+
+                                read(pfd[0].fd, buf, sizeof(buf));
+                        }
+                } else {
+                        sleep(1);
+                }
+
+                if (timeout > 0) {
+                        unsigned long long age_usec;
+
+                        age_usec = now_usec() - start_usec;
+                        if (age_usec / (1000 * 1000) >= timeout) {
+                                struct udev_list_entry *list_entry;
+
+                                if (!quiet && udev_queue_get_queued_list_entry(udev_queue) != NULL) {
+                                        info(udev, "timeout waiting for udev queue\n");
+                                        printf("\nudevadm settle - timeout of %i seconds reached, the event queue contains:\n", timeout);
+                                        udev_list_entry_foreach(list_entry, udev_queue_get_queued_list_entry(udev_queue))
+                                                printf("  %s (%s)\n",
+                                                udev_list_entry_get_name(list_entry),
+                                                udev_list_entry_get_value(list_entry));
+                                }
+
+                                break;
+                        }
+                }
+        }
+out:
+        if (pfd[0].fd >= 0)
+                close(pfd[0].fd);
+        udev_queue_unref(udev_queue);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_settle = {
+        .name = "settle",
+        .cmd = adm_settle,
+        .help = "wait for the event queue to finish",
+};
diff --git a/src/udevadm-test-builtin.c b/src/udevadm-test-builtin.c
new file mode 100644
index 0000000..3a49f7c
--- /dev/null
+++ b/src/udevadm-test-builtin.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+static void help(struct udev *udev)
+{
+        fprintf(stderr, "\n");
+        fprintf(stderr, "Usage: udevadm builtin [--help] <command> <syspath>\n");
+        udev_builtin_list(udev);
+        fprintf(stderr, "\n");
+}
+
+static int adm_builtin(struct udev *udev, int argc, char *argv[])
+{
+        static const struct option options[] = {
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+        char *command = NULL;
+        char *syspath = NULL;
+        char filename[UTIL_PATH_SIZE];
+        struct udev_device *dev = NULL;
+        enum udev_builtin_cmd cmd;
+        int rc = EXIT_SUCCESS;
+
+        dbg(udev, "version %s\n", VERSION);
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "h", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'h':
+                        help(udev);
+                        goto out;
+                }
+        }
+
+        command = argv[optind++];
+        if (command == NULL) {
+                fprintf(stderr, "command missing\n");
+                help(udev);
+                rc = 2;
+                goto out;
+        }
+
+        syspath = argv[optind++];
+        if (syspath == NULL) {
+                fprintf(stderr, "syspath missing\n\n");
+                rc = 3;
+                goto out;
+        }
+
+        udev_builtin_init(udev);
+
+        cmd = udev_builtin_lookup(command);
+        if (cmd >= UDEV_BUILTIN_MAX) {
+                fprintf(stderr, "unknown command '%s'\n", command);
+                help(udev);
+                rc = 5;
+                goto out;
+        }
+
+        /* add /sys if needed */
+        if (strncmp(syspath, udev_get_sys_path(udev), strlen(udev_get_sys_path(udev))) != 0)
+                util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), syspath, NULL);
+        else
+                util_strscpy(filename, sizeof(filename), syspath);
+        util_remove_trailing_chars(filename, '/');
+
+        dev = udev_device_new_from_syspath(udev, filename);
+        if (dev == NULL) {
+                fprintf(stderr, "unable to open device '%s'\n\n", filename);
+                rc = 4;
+                goto out;
+        }
+
+        if (udev_builtin_run(dev, cmd, command, true) < 0) {
+                fprintf(stderr, "error executing '%s'\n\n", command);
+                rc = 6;
+        }
+out:
+        udev_device_unref(dev);
+        udev_builtin_exit(udev);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_test_builtin = {
+        .name = "test-builtin",
+        .cmd = adm_builtin,
+        .help = "test a built-in command",
+        .debug = true,
+};
diff --git a/src/udevadm-test.c b/src/udevadm-test.c
new file mode 100644
index 0000000..6275cff
--- /dev/null
+++ b/src/udevadm-test.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <sys/signalfd.h>
+
+#include "udev.h"
+
+static int adm_test(struct udev *udev, int argc, char *argv[])
+{
+        int resolve_names = 1;
+        char filename[UTIL_PATH_SIZE];
+        const char *action = "add";
+        const char *syspath = NULL;
+        struct udev_event *event = NULL;
+        struct udev_device *dev = NULL;
+        struct udev_rules *rules = NULL;
+        struct udev_list_entry *entry;
+        sigset_t mask, sigmask_orig;
+        int err;
+        int rc = 0;
+
+        static const struct option options[] = {
+                { "action", required_argument, NULL, 'a' },
+                { "resolve-names", required_argument, NULL, 'N' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+
+        info(udev, "version %s\n", VERSION);
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "a:s:N:fh", options, NULL);
+                if (option == -1)
+                        break;
+
+                dbg(udev, "option '%c'\n", option);
+                switch (option) {
+                case 'a':
+                        action = optarg;
+                        break;
+                case 'N':
+                        if (strcmp (optarg, "early") == 0) {
+                                resolve_names = 1;
+                        } else if (strcmp (optarg, "late") == 0) {
+                                resolve_names = 0;
+                        } else if (strcmp (optarg, "never") == 0) {
+                                resolve_names = -1;
+                        } else {
+                                fprintf(stderr, "resolve-names must be early, late or never\n");
+                                err(udev, "resolve-names must be early, late or never\n");
+                                exit(EXIT_FAILURE);
+                        }
+                        break;
+                case 'h':
+                        printf("Usage: udevadm test OPTIONS <syspath>\n"
+                               "  --action=<string>     set action string\n"
+                               "  --help\n\n");
+                        exit(EXIT_SUCCESS);
+                default:
+                        exit(EXIT_FAILURE);
+                }
+        }
+        syspath = argv[optind];
+
+        if (syspath == NULL) {
+                fprintf(stderr, "syspath parameter missing\n");
+                rc = 2;
+                goto out;
+        }
+
+        printf("This program is for debugging only, it does not run any program,\n"
+               "specified by a RUN key. It may show incorrect results, because\n"
+               "some values may be different, or not available at a simulation run.\n"
+               "\n");
+
+        sigprocmask(SIG_SETMASK, NULL, &sigmask_orig);
+
+        udev_builtin_init(udev);
+
+        rules = udev_rules_new(udev, resolve_names);
+        if (rules == NULL) {
+                fprintf(stderr, "error reading rules\n");
+                rc = 3;
+                goto out;
+        }
+
+        /* add /sys if needed */
+        if (strncmp(syspath, udev_get_sys_path(udev), strlen(udev_get_sys_path(udev))) != 0)
+                util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), syspath, NULL);
+        else
+                util_strscpy(filename, sizeof(filename), syspath);
+        util_remove_trailing_chars(filename, '/');
+
+        dev = udev_device_new_from_syspath(udev, filename);
+        if (dev == NULL) {
+                fprintf(stderr, "unable to open device '%s'\n", filename);
+                rc = 4;
+                goto out;
+        }
+
+        /* skip reading of db, but read kernel parameters */
+        udev_device_set_info_loaded(dev);
+        udev_device_read_uevent_file(dev);
+
+        udev_device_set_action(dev, action);
+        event = udev_event_new(dev);
+
+        sigfillset(&mask);
+        sigprocmask(SIG_SETMASK, &mask, &sigmask_orig);
+        event->fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+        if (event->fd_signal < 0) {
+                fprintf(stderr, "error creating signalfd\n");
+                rc = 5;
+                goto out;
+        }
+
+        err = udev_event_execute_rules(event, rules, &sigmask_orig);
+
+        udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev))
+                printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry));
+
+        if (err == 0) {
+                udev_list_entry_foreach(entry, udev_list_get_entry(&event->run_list)) {
+                        char program[UTIL_PATH_SIZE];
+
+                        udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program));
+                        printf("run: '%s'\n", program);
+                }
+        }
+out:
+        if (event != NULL && event->fd_signal >= 0)
+                close(event->fd_signal);
+        udev_event_unref(event);
+        udev_device_unref(dev);
+        udev_rules_unref(rules);
+        udev_builtin_exit(udev);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_test = {
+        .name = "test",
+        .cmd = adm_test,
+        .help = "test an event run",
+        .debug = true,
+};
diff --git a/src/udevadm-trigger.c b/src/udevadm-trigger.c
new file mode 100644
index 0000000..3cce23d
--- /dev/null
+++ b/src/udevadm-trigger.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2008-2009 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "udev.h"
+
+static int verbose;
+static int dry_run;
+
+static void exec_list(struct udev_enumerate *udev_enumerate, const char *action)
+{
+        struct udev *udev = udev_enumerate_get_udev(udev_enumerate);
+        struct udev_list_entry *entry;
+
+        udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_enumerate)) {
+                char filename[UTIL_PATH_SIZE];
+                int fd;
+
+                if (verbose)
+                        printf("%s\n", udev_list_entry_get_name(entry));
+                if (dry_run)
+                        continue;
+                util_strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL);
+                fd = open(filename, O_WRONLY);
+                if (fd < 0) {
+                        dbg(udev, "error on opening %s: %m\n", filename);
+                        continue;
+                }
+                if (write(fd, action, strlen(action)) < 0)
+                        info(udev, "error writing '%s' to '%s': %m\n", action, filename);
+                close(fd);
+        }
+}
+
+static const char *keyval(const char *str, const char **val, char *buf, size_t size)
+{
+        char *pos;
+
+        util_strscpy(buf, size,str);
+        pos = strchr(buf, '=');
+        if (pos != NULL) {
+                pos[0] = 0;
+                pos++;
+        }
+        *val = pos;
+        return buf;
+}
+
+static int adm_trigger(struct udev *udev, int argc, char *argv[])
+{
+        static const struct option options[] = {
+                { "verbose", no_argument, NULL, 'v' },
+                { "dry-run", no_argument, NULL, 'n' },
+                { "type", required_argument, NULL, 't' },
+                { "action", required_argument, NULL, 'c' },
+                { "subsystem-match", required_argument, NULL, 's' },
+                { "subsystem-nomatch", required_argument, NULL, 'S' },
+                { "attr-match", required_argument, NULL, 'a' },
+                { "attr-nomatch", required_argument, NULL, 'A' },
+                { "property-match", required_argument, NULL, 'p' },
+                { "tag-match", required_argument, NULL, 'g' },
+                { "sysname-match", required_argument, NULL, 'y' },
+                { "parent-match", required_argument, NULL, 'b' },
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+        enum {
+                TYPE_DEVICES,
+                TYPE_SUBSYSTEMS,
+        } device_type = TYPE_DEVICES;
+        const char *action = "change";
+        struct udev_enumerate *udev_enumerate;
+        int rc = 0;
+
+        dbg(udev, "version %s\n", VERSION);
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL) {
+                rc = 1;
+                goto exit;
+        }
+
+        for (;;) {
+                int option;
+                const char *key;
+                const char *val;
+                char buf[UTIL_PATH_SIZE];
+
+                option = getopt_long(argc, argv, "vng:o:t:hc:p:s:S:a:A:y:b:", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'v':
+                        verbose = 1;
+                        break;
+                case 'n':
+                        dry_run = 1;
+                        break;
+                case 't':
+                        if (strcmp(optarg, "devices") == 0) {
+                                device_type = TYPE_DEVICES;
+                        } else if (strcmp(optarg, "subsystems") == 0) {
+                                device_type = TYPE_SUBSYSTEMS;
+                        } else {
+                                err(udev, "unknown type --type=%s\n", optarg);
+                                rc = 2;
+                                goto exit;
+                        }
+                        break;
+                case 'c':
+                        action = optarg;
+                        break;
+                case 's':
+                        udev_enumerate_add_match_subsystem(udev_enumerate, optarg);
+                        break;
+                case 'S':
+                        udev_enumerate_add_nomatch_subsystem(udev_enumerate, optarg);
+                        break;
+                case 'a':
+                        key = keyval(optarg, &val, buf, sizeof(buf));
+                        udev_enumerate_add_match_sysattr(udev_enumerate, key, val);
+                        break;
+                case 'A':
+                        key = keyval(optarg, &val, buf, sizeof(buf));
+                        udev_enumerate_add_nomatch_sysattr(udev_enumerate, key, val);
+                        break;
+                case 'p':
+                        key = keyval(optarg, &val, buf, sizeof(buf));
+                        udev_enumerate_add_match_property(udev_enumerate, key, val);
+                        break;
+                case 'g':
+                        udev_enumerate_add_match_tag(udev_enumerate, optarg);
+                        break;
+                case 'y':
+                        udev_enumerate_add_match_sysname(udev_enumerate, optarg);
+                        break;
+                case 'b': {
+                        char path[UTIL_PATH_SIZE];
+                        struct udev_device *dev;
+
+                        /* add sys dir if needed */
+                        if (strncmp(optarg, udev_get_sys_path(udev), strlen(udev_get_sys_path(udev))) != 0)
+                                util_strscpyl(path, sizeof(path), udev_get_sys_path(udev), optarg, NULL);
+                        else
+                                util_strscpy(path, sizeof(path), optarg);
+                        util_remove_trailing_chars(path, '/');
+                        dev = udev_device_new_from_syspath(udev, path);
+                        if (dev == NULL) {
+                                err(udev, "unable to open the device '%s'\n", optarg);
+                                rc = 2;
+                                goto exit;
+                        }
+                        udev_enumerate_add_match_parent(udev_enumerate, dev);
+                        /* drop reference immediately, enumerate pins the device as long as needed */
+                        udev_device_unref(dev);
+                        break;
+                }
+                case 'h':
+                        printf("Usage: udevadm trigger OPTIONS\n"
+                               "  --verbose                       print the list of devices while running\n"
+                               "  --dry-run                       do not actually trigger the events\n"
+                               "  --type=                         type of events to trigger\n"
+                               "      devices                       sys devices (default)\n"
+                               "      subsystems                    sys subsystems and drivers\n"
+                               "  --action=<action>               event action value, default is \"change\"\n"
+                               "  --subsystem-match=<subsystem>   trigger devices from a matching subsystem\n"
+                               "  --subsystem-nomatch=<subsystem> exclude devices from a matching subsystem\n"
+                               "  --attr-match=<file[=<value>]>   trigger devices with a matching attribute\n"
+                               "  --attr-nomatch=<file[=<value>]> exclude devices with a matching attribute\n"
+                               "  --property-match=<key>=<value>  trigger devices with a matching property\n"
+                               "  --tag-match=<key>=<value>       trigger devices with a matching property\n"
+                               "  --sysname-match=<name>          trigger devices with a matching name\n"
+                               "  --parent-match=<name>           trigger devices with that parent device\n"
+                               "  --help\n\n");
+                        goto exit;
+                default:
+                        rc = 1;
+                        goto exit;
+                }
+        }
+
+        switch (device_type) {
+        case TYPE_SUBSYSTEMS:
+                udev_enumerate_scan_subsystems(udev_enumerate);
+                exec_list(udev_enumerate, action);
+                goto exit;
+        case TYPE_DEVICES:
+                udev_enumerate_scan_devices(udev_enumerate);
+                exec_list(udev_enumerate, action);
+                goto exit;
+        default:
+                goto exit;
+        }
+exit:
+        udev_enumerate_unref(udev_enumerate);
+        return rc;
+}
+
+const struct udevadm_cmd udevadm_trigger = {
+        .name = "trigger",
+        .cmd = adm_trigger,
+        .help = "request events from the kernel",
+};
diff --git a/src/udevadm.c b/src/udevadm.c
new file mode 100644
index 0000000..224ece0
--- /dev/null
+++ b/src/udevadm.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2007-2009 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include "udev.h"
+
+static bool debug;
+
+void udev_main_log(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        if (debug) {
+                fprintf(stderr, "%s: ", fn);
+                vfprintf(stderr, format, args);
+        } else {
+                va_list args2;
+
+                va_copy(args2, args);
+                vfprintf(stderr, format, args2);
+                va_end(args2);
+                vsyslog(priority, format, args);
+        }
+}
+
+static int adm_version(struct udev *udev, int argc, char *argv[])
+{
+        printf("%s\n", VERSION);
+        return 0;
+}
+static const struct udevadm_cmd udevadm_version = {
+        .name = "version",
+        .cmd = adm_version,
+};
+
+static int adm_help(struct udev *udev, int argc, char *argv[]);
+static const struct udevadm_cmd udevadm_help = {
+        .name = "help",
+        .cmd = adm_help,
+};
+
+static const struct udevadm_cmd *udevadm_cmds[] = {
+        &udevadm_info,
+        &udevadm_trigger,
+        &udevadm_settle,
+        &udevadm_control,
+        &udevadm_monitor,
+        &udevadm_test,
+        &udevadm_test_builtin,
+        &udevadm_version,
+        &udevadm_help,
+};
+
+static int adm_help(struct udev *udev, int argc, char *argv[])
+{
+        unsigned int i;
+
+        fprintf(stderr, "Usage: udevadm [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n");
+        for (i = 0; i < ARRAY_SIZE(udevadm_cmds); i++)
+                if (udevadm_cmds[i]->help != NULL)
+                        printf("  %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help);
+        fprintf(stderr, "\n");
+        return 0;
+}
+
+static int run_command(struct udev *udev, const struct udevadm_cmd *cmd, int argc, char *argv[])
+{
+        if (cmd->debug) {
+                debug = true;
+                if (udev_get_log_priority(udev) < LOG_INFO)
+                        udev_set_log_priority(udev, LOG_INFO);
+        }
+        info(udev, "calling: %s\n", cmd->name);
+        return cmd->cmd(udev, argc, argv);
+}
+
+int main(int argc, char *argv[])
+{
+        struct udev *udev;
+        static const struct option options[] = {
+                { "debug", no_argument, NULL, 'd' },
+                { "help", no_argument, NULL, 'h' },
+                { "version", no_argument, NULL, 'V' },
+                {}
+        };
+        const char *command;
+        unsigned int i;
+        int rc = 1;
+
+        udev = udev_new();
+        if (udev == NULL)
+                goto out;
+
+        udev_log_init("udevadm");
+        udev_set_log_fn(udev, udev_main_log);
+        udev_selinux_init(udev);
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "+dhV", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'd':
+                        debug = true;
+                        if (udev_get_log_priority(udev) < LOG_INFO)
+                                udev_set_log_priority(udev, LOG_INFO);
+                        break;
+                case 'h':
+                        rc = adm_help(udev, argc, argv);
+                        goto out;
+                case 'V':
+                        rc = adm_version(udev, argc, argv);
+                        goto out;
+                default:
+                        goto out;
+                }
+        }
+        command = argv[optind];
+
+        info(udev, "runtime dir '%s'\n", udev_get_run_path(udev));
+
+        if (command != NULL)
+                for (i = 0; i < ARRAY_SIZE(udevadm_cmds); i++) {
+                        if (strcmp(udevadm_cmds[i]->name, command) == 0) {
+                                argc -= optind;
+                                argv += optind;
+                                optind = 0;
+                                rc = run_command(udev, udevadm_cmds[i], argc, argv);
+                                goto out;
+                        }
+                }
+
+        fprintf(stderr, "missing or unknown command\n\n");
+        adm_help(udev, argc, argv);
+        rc = 2;
+out:
+        udev_selinux_exit(udev);
+        udev_unref(udev);
+        udev_log_close();
+        return rc;
+}
diff --git a/src/udevadm.xml b/src/udevadm.xml
new file mode 100644
index 0000000..455ce80
--- /dev/null
+++ b/src/udevadm.xml
@@ -0,0 +1,472 @@
+<?xml version='1.0'?>
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="udevadm">
+  <refentryinfo>
+    <title>udevadm</title>
+    <productname>udev</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>udevadm</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo class="version"></refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>udevadm</refname><refpurpose>udev management tool</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>udevadm</command>
+        <arg><option>--debug</option></arg>
+        <arg><option>--version</option></arg>
+        <arg><option>--help</option></arg>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm info <replaceable>options</replaceable></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm trigger <optional>options</optional></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm settle <optional>options</optional></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm control <replaceable>command</replaceable></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm monitor <optional>options</optional></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm test <optional>options</optional> <replaceable>devpath</replaceable></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></command>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1><title>Description</title>
+    <para>udevadm expects a command and command specific options.  It
+    controls the runtime behavior of udev, requests kernel events,
+    manages the event queue, and provides simple debugging mechanisms.</para>
+  </refsect1>
+
+  <refsect1><title>OPTIONS</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>--debug</option></term>
+        <listitem>
+          <para>Print debug messages to stderr.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--version</option></term>
+        <listitem>
+          <para>Print version number.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--help</option></term>
+        <listitem>
+          <para>Print help text.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+
+    <refsect2><title>udevadm info <replaceable>options</replaceable></title>
+      <para>Queries the udev database for device information
+      stored in the udev database. It can also query the properties
+      of a device from its sysfs representation to help creating udev
+      rules that match this device.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--query=<replaceable>type</replaceable></option></term>
+          <listitem>
+            <para>Query the database for specified type of device data. It needs the
+            <option>--path</option> or <option>--name</option> to identify the specified
+            device. Valid queries are:
+            <command>name</command>, <command>symlink</command>, <command>path</command>,
+            <command>property</command>, <command>all</command>.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--path=<replaceable>devpath</replaceable></option></term>
+          <listitem>
+            <para>The devpath of the device to query.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--name=<replaceable>file</replaceable></option></term>
+          <listitem>
+            <para>The name of the device node or a symlink to query</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--root</option></term>
+          <listitem>
+            <para>The udev root directory: <filename>/dev</filename>. If used in conjunction
+            with a <command>name</command> or <command>symlink</command> query, the
+            query returns the absolute path including the root directory.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--run</option></term>
+          <listitem>
+            <para>The udev runtime directory: <filename>/run/udev</filename>.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--attribute-walk</option></term>
+          <listitem>
+            <para>Print all sysfs properties of the specified device that can be used
+            in udev rules to match the specified device. It prints all devices
+            along the chain, up to the root of sysfs that can be used in udev rules.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--export</option></term>
+          <listitem>
+            <para>Print output as key/value pairs. Values are enclosed in single quotes.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--export-prefix=<replaceable>name</replaceable></option></term>
+          <listitem>
+            <para>Add a prefix to the key name of exported values.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--device-id-of-file=<replaceable>file</replaceable></option></term>
+          <listitem>
+            <para>Print major/minor numbers of the underlying device, where the file
+            lives on.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--export-db</option></term>
+          <listitem>
+            <para>Export the content of the udev database.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--cleanup-db</option></term>
+          <listitem>
+            <para>Cleanup the udev database.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--version</option></term>
+          <listitem>
+            <para>Print version.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--help</option></term>
+          <listitem>
+            <para>Print help text.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>udevadm trigger <optional>options</optional></title>
+      <para>Request device events from the kernel. Primarily used to replay events at system coldplug time.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--verbose</option></term>
+          <listitem>
+            <para>Print the list of devices which will be triggered.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--dry-run</option></term>
+          <listitem>
+            <para>Do not actually trigger the event.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--type=<replaceable>type</replaceable></option></term>
+          <listitem>
+            <para>Trigger a specific type of devices. Valid types are:
+            <command>devices</command>, <command>subsystems</command>.
+            The default value is <command>devices</command>.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--action=<replaceable>action</replaceable></option></term>
+          <listitem>
+            <para>Type of event to be triggered. The default value is <command>change</command>.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--subsystem-match=<replaceable>subsystem</replaceable></option></term>
+          <listitem>
+            <para>Trigger events for devices which belong to a matching subsystem. This option
+            can be specified multiple times and supports shell style pattern matching.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--subsystem-nomatch=<replaceable>subsystem</replaceable></option></term>
+          <listitem>
+            <para>Do not trigger events for devices which belong to a matching subsystem. This option
+            can be specified multiple times and supports shell style pattern matching.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--attr-match=<replaceable>attribute</replaceable>=<replaceable>value</replaceable></option></term>
+          <listitem>
+            <para>Trigger events for devices with a matching sysfs attribute. If a value is specified
+            along with the attribute name, the content of the attribute is matched against the given
+            value using shell style pattern matching. If no value is specified, the existence of the
+            sysfs attribute is checked. This option can be specified multiple times.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--attr-nomatch=<replaceable>attribute</replaceable>=<replaceable>value</replaceable></option></term>
+          <listitem>
+            <para>Do not trigger events for devices with a matching sysfs attribute. If a value is
+            specified along with the attribute name, the content of the attribute is matched against
+            the given value using shell style pattern matching. If no value is specified, the existence
+            of the sysfs attribute is checked. This option can be specified multiple times.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--property-match=<replaceable>property</replaceable>=<replaceable>value</replaceable></option></term>
+          <listitem>
+            <para>Trigger events for devices with a matching property value. This option can be
+            specified multiple times and supports shell style pattern matching.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--tag-match=<replaceable>property</replaceable></option></term>
+          <listitem>
+            <para>Trigger events for devices with a matching tag. This option can be
+            specified multiple times.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--sysname-match=<replaceable>name</replaceable></option></term>
+          <listitem>
+            <para>Trigger events for devices with a matching sys device name. This option can be
+            specified multiple times and supports shell style pattern matching.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--parent-match=<replaceable>syspath</replaceable></option></term>
+          <listitem>
+            <para>Trigger events for all children of a given device.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>udevadm settle <optional>options</optional></title>
+      <para>Watches the udev event queue, and exits if all current events are handled.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--timeout=<replaceable>seconds</replaceable></option></term>
+          <listitem>
+            <para>Maximum number of seconds to wait for the event queue to become empty.
+            The default value is 120 seconds. A value of 0 will check if the queue is empty
+            and always return immediately.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--seq-start=<replaceable>seqnum</replaceable></option></term>
+          <listitem>
+            <para>Wait only for events after the given sequence number.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--seq-end=<replaceable>seqnum</replaceable></option></term>
+          <listitem>
+            <para>Wait only for events before the given sequence number.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--exit-if-exists=<replaceable>file</replaceable></option></term>
+          <listitem>
+            <para>Stop waiting if file exists.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--quiet</option></term>
+          <listitem>
+            <para>Do not print any output, like the remaining queue entries when reaching the timeout.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--help</option></term>
+          <listitem>
+            <para>Print help text.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>udevadm control <replaceable>command</replaceable></title>
+      <para>Modify the internal state of the running udev daemon.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--exit</option></term>
+          <listitem>
+            <para>Signal and wait for udevd to exit.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--log-priority=<replaceable>value</replaceable></option></term>
+          <listitem>
+            <para>Set the internal log level of udevd. Valid values are the numerical
+            syslog priorities or their textual representations: <option>err</option>,
+            <option>info</option> and <option>debug</option>.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--stop-exec-queue</option></term>
+          <listitem>
+            <para>Signal udevd to stop executing new events. Incoming events
+            will be queued.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--start-exec-queue</option></term>
+          <listitem>
+            <para>Signal udevd to enable the execution of events.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--reload</option></term>
+          <listitem>
+            <para>Signal udevd to reload the rules files and other databases like the kernel
+            module index. Reloading rules and databases does not apply any changes to already
+            existing devices; the new configuration will only be applied to new events.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--property=<replaceable>KEY</replaceable>=<replaceable>value</replaceable></option></term>
+          <listitem>
+            <para>Set a global property for all events.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--children-max=</option><replaceable>value</replaceable></term>
+          <listitem>
+            <para>Set the maximum number of events, udevd will handle at the
+            same time.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--timeout=</option><replaceable>seconds</replaceable></term>
+          <listitem>
+            <para>The maximum number seconds to wait for a reply from udevd.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--help</option></term>
+          <listitem>
+            <para>Print help text.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>udevadm monitor <optional>options</optional></title>
+      <para>Listens to the kernel uevents and events sent out by a udev rule
+      and prints the devpath of the event to the console. It can be used to analyze the
+      event timing, by comparing the timestamps of the kernel uevent and the udev event.
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--kernel</option></term>
+          <listitem>
+            <para>Print the kernel uevents.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--udev</option></term>
+          <listitem>
+            <para>Print the udev event after the rule processing.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--property</option></term>
+          <listitem>
+            <para>Also print the properties of the event.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--subsystem-match=<replaceable>string[/string]</replaceable></option></term>
+          <listitem>
+            <para>Filter events by subsystem[/devtype]. Only udev events with a matching subsystem value will pass.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--tag-match=<replaceable>string</replaceable></option></term>
+          <listitem>
+            <para>Filter events by property. Only udev events with a given tag attached will pass.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--help</option></term>
+          <listitem>
+            <para>Print help text.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>udevadm test <optional>options</optional> <replaceable>devpath</replaceable></title>
+      <para>Simulate a udev event run for the given device, and print debug output.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--action=<replaceable>string</replaceable></option></term>
+          <listitem>
+            <para>The action string.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--subsystem=<replaceable>string</replaceable></option></term>
+          <listitem>
+            <para>The subsystem string.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><option>--help</option></term>
+          <listitem>
+            <para>Print help text.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2><title>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></title>
+      <para>Run a built-in command for the given device, and print debug output.</para>
+      <variablelist>
+        <varlistentry>
+          <term><option>--help</option></term>
+          <listitem>
+            <para>Print help text.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+  </refsect1>
+
+  <refsect1><title>Author</title>
+    <para>Written by Kay Sievers <email>kay.sievers@vrfy.org</email>.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry>
+        <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+    </citerefentry>
+    <citerefentry>
+        <refentrytitle>udevd</refentrytitle><manvolnum>8</manvolnum>
+    </citerefentry></para>
+  </refsect1>
+</refentry>
diff --git a/src/udevd.c b/src/udevd.c
new file mode 100644
index 0000000..1702217
--- /dev/null
+++ b/src/udevd.c
@@ -0,0 +1,1746 @@
+/*
+ * Copyright (C) 2004-2011 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <time.h>
+#include <getopt.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/signalfd.h>
+#include <sys/epoll.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/inotify.h>
+#include <sys/utsname.h>
+
+#include "udev.h"
+#include "sd-daemon.h"
+
+static bool debug;
+
+void udev_main_log(struct udev *udev, int priority,
+                   const char *file, int line, const char *fn,
+                   const char *format, va_list args)
+{
+        if (debug) {
+                char buf[1024];
+                struct timespec ts;
+
+                vsnprintf(buf, sizeof(buf), format, args);
+                clock_gettime(CLOCK_MONOTONIC, &ts);
+                fprintf(stderr, "[%llu.%06u] [%u] %s: %s",
+                        (unsigned long long) ts.tv_sec, (unsigned int) ts.tv_nsec/1000,
+                        (int) getpid(), fn, buf);
+        } else {
+                vsyslog(priority, format, args);
+        }
+}
+
+static struct udev_rules *rules;
+static struct udev_queue_export *udev_queue_export;
+static struct udev_ctrl *udev_ctrl;
+static struct udev_monitor *monitor;
+static int worker_watch[2] = { -1, -1 };
+static int fd_signal = -1;
+static int fd_ep = -1;
+static int fd_inotify = -1;
+static bool stop_exec_queue;
+static bool reload;
+static int children;
+static int children_max;
+static int exec_delay;
+static sigset_t sigmask_orig;
+static UDEV_LIST(event_list);
+static UDEV_LIST(worker_list);
+static bool udev_exit;
+
+enum event_state {
+        EVENT_UNDEF,
+        EVENT_QUEUED,
+        EVENT_RUNNING,
+};
+
+struct event {
+        struct udev_list_node node;
+        struct udev *udev;
+        struct udev_device *dev;
+        enum event_state state;
+        int exitcode;
+        unsigned long long int delaying_seqnum;
+        unsigned long long int seqnum;
+        const char *devpath;
+        size_t devpath_len;
+        const char *devpath_old;
+        dev_t devnum;
+        bool is_block;
+        int ifindex;
+};
+
+static struct event *node_to_event(struct udev_list_node *node)
+{
+        char *event;
+
+        event = (char *)node;
+        event -= offsetof(struct event, node);
+        return (struct event *)event;
+}
+
+static void event_queue_cleanup(struct udev *udev, enum event_state type);
+
+enum worker_state {
+        WORKER_UNDEF,
+        WORKER_RUNNING,
+        WORKER_IDLE,
+        WORKER_KILLED,
+};
+
+struct worker {
+        struct udev_list_node node;
+        struct udev *udev;
+        int refcount;
+        pid_t pid;
+        struct udev_monitor *monitor;
+        enum worker_state state;
+        struct event *event;
+        unsigned long long event_start_usec;
+};
+
+/* passed from worker to main process */
+struct worker_message {
+        pid_t pid;
+        int exitcode;
+};
+
+static struct worker *node_to_worker(struct udev_list_node *node)
+{
+        char *worker;
+
+        worker = (char *)node;
+        worker -= offsetof(struct worker, node);
+        return (struct worker *)worker;
+}
+
+static void event_queue_delete(struct event *event, bool export)
+{
+        udev_list_node_remove(&event->node);
+
+        if (export) {
+                udev_queue_export_device_finished(udev_queue_export, event->dev);
+                info(event->udev, "seq %llu done with %i\n", udev_device_get_seqnum(event->dev), event->exitcode);
+        }
+        udev_device_unref(event->dev);
+        free(event);
+}
+
+static struct worker *worker_ref(struct worker *worker)
+{
+        worker->refcount++;
+        return worker;
+}
+
+static void worker_cleanup(struct worker *worker)
+{
+        udev_list_node_remove(&worker->node);
+        udev_monitor_unref(worker->monitor);
+        children--;
+        free(worker);
+}
+
+static void worker_unref(struct worker *worker)
+{
+        worker->refcount--;
+        if (worker->refcount > 0)
+                return;
+        info(worker->udev, "worker [%u] cleaned up\n", worker->pid);
+        worker_cleanup(worker);
+}
+
+static void worker_list_cleanup(struct udev *udev)
+{
+        struct udev_list_node *loop, *tmp;
+
+        udev_list_node_foreach_safe(loop, tmp, &worker_list) {
+                struct worker *worker = node_to_worker(loop);
+
+                worker_cleanup(worker);
+        }
+}
+
+static void worker_new(struct event *event)
+{
+        struct udev *udev = event->udev;
+        struct worker *worker;
+        struct udev_monitor *worker_monitor;
+        pid_t pid;
+
+        /* listen for new events */
+        worker_monitor = udev_monitor_new_from_netlink(udev, NULL);
+        if (worker_monitor == NULL)
+                return;
+        /* allow the main daemon netlink address to send devices to the worker */
+        udev_monitor_allow_unicast_sender(worker_monitor, monitor);
+        udev_monitor_enable_receiving(worker_monitor);
+
+        worker = calloc(1, sizeof(struct worker));
+        if (worker == NULL) {
+                udev_monitor_unref(worker_monitor);
+                return;
+        }
+        /* worker + event reference */
+        worker->refcount = 2;
+        worker->udev = udev;
+
+        pid = fork();
+        switch (pid) {
+        case 0: {
+                struct udev_device *dev = NULL;
+                int fd_monitor;
+                struct epoll_event ep_signal, ep_monitor;
+                sigset_t mask;
+                int rc = EXIT_SUCCESS;
+
+                /* take initial device from queue */
+                dev = event->dev;
+                event->dev = NULL;
+
+                free(worker);
+                worker_list_cleanup(udev);
+                event_queue_cleanup(udev, EVENT_UNDEF);
+                udev_queue_export_unref(udev_queue_export);
+                udev_monitor_unref(monitor);
+                udev_ctrl_unref(udev_ctrl);
+                close(fd_signal);
+                close(fd_ep);
+                close(worker_watch[READ_END]);
+
+                sigfillset(&mask);
+                fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+                if (fd_signal < 0) {
+                        err(udev, "error creating signalfd %m\n");
+                        rc = 2;
+                        goto out;
+                }
+
+                fd_ep = epoll_create1(EPOLL_CLOEXEC);
+                if (fd_ep < 0) {
+                        err(udev, "error creating epoll fd: %m\n");
+                        rc = 3;
+                        goto out;
+                }
+
+                memset(&ep_signal, 0, sizeof(struct epoll_event));
+                ep_signal.events = EPOLLIN;
+                ep_signal.data.fd = fd_signal;
+
+                fd_monitor = udev_monitor_get_fd(worker_monitor);
+                memset(&ep_monitor, 0, sizeof(struct epoll_event));
+                ep_monitor.events = EPOLLIN;
+                ep_monitor.data.fd = fd_monitor;
+
+                if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 ||
+                    epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_monitor, &ep_monitor) < 0) {
+                        err(udev, "fail to add fds to epoll: %m\n");
+                        rc = 4;
+                        goto out;
+                }
+
+                /* request TERM signal if parent exits */
+                prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+                for (;;) {
+                        struct udev_event *udev_event;
+                        struct worker_message msg;
+                        int err;
+
+                        info(udev, "seq %llu running\n", udev_device_get_seqnum(dev));
+                        udev_event = udev_event_new(dev);
+                        if (udev_event == NULL) {
+                                rc = 5;
+                                goto out;
+                        }
+
+                        /* needed for SIGCHLD/SIGTERM in spawn() */
+                        udev_event->fd_signal = fd_signal;
+
+                        if (exec_delay > 0)
+                                udev_event->exec_delay = exec_delay;
+
+                        /* apply rules, create node, symlinks */
+                        err = udev_event_execute_rules(udev_event, rules, &sigmask_orig);
+
+                        if (err == 0)
+                                udev_event_execute_run(udev_event, &sigmask_orig);
+
+                        /* apply/restore inotify watch */
+                        if (err == 0 && udev_event->inotify_watch) {
+                                udev_watch_begin(udev, dev);
+                                udev_device_update_db(dev);
+                        }
+
+                        /* send processed event back to libudev listeners */
+                        udev_monitor_send_device(worker_monitor, NULL, dev);
+
+                        /* send udevd the result of the event execution */
+                        memset(&msg, 0, sizeof(struct worker_message));
+                        if (err != 0)
+                                msg.exitcode = err;
+                        msg.pid = getpid();
+                        send(worker_watch[WRITE_END], &msg, sizeof(struct worker_message), 0);
+
+                        info(udev, "seq %llu processed with %i\n", udev_device_get_seqnum(dev), err);
+
+                        udev_device_unref(dev);
+                        dev = NULL;
+
+                        if (udev_event->sigterm) {
+                                udev_event_unref(udev_event);
+                                goto out;
+                        }
+
+                        udev_event_unref(udev_event);
+
+                        /* wait for more device messages from main udevd, or term signal */
+                        while (dev == NULL) {
+                                struct epoll_event ev[4];
+                                int fdcount;
+                                int i;
+
+                                fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), -1);
+                                if (fdcount < 0) {
+                                        if (errno == EINTR)
+                                                continue;
+                                        err = -errno;
+                                        err(udev, "failed to poll: %m\n");
+                                        goto out;
+                                }
+
+                                for (i = 0; i < fdcount; i++) {
+                                        if (ev[i].data.fd == fd_monitor && ev[i].events & EPOLLIN) {
+                                                dev = udev_monitor_receive_device(worker_monitor);
+                                                break;
+                                        } else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN) {
+                                                struct signalfd_siginfo fdsi;
+                                                ssize_t size;
+
+                                                size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo));
+                                                if (size != sizeof(struct signalfd_siginfo))
+                                                        continue;
+                                                switch (fdsi.ssi_signo) {
+                                                case SIGTERM:
+                                                        goto out;
+                                                }
+                                        }
+                                }
+                        }
+                }
+out:
+                udev_device_unref(dev);
+                if (fd_signal >= 0)
+                        close(fd_signal);
+                if (fd_ep >= 0)
+                        close(fd_ep);
+                close(fd_inotify);
+                close(worker_watch[WRITE_END]);
+                udev_rules_unref(rules);
+                udev_builtin_exit(udev);
+                udev_monitor_unref(worker_monitor);
+                udev_unref(udev);
+                udev_log_close();
+                exit(rc);
+        }
+        case -1:
+                udev_monitor_unref(worker_monitor);
+                event->state = EVENT_QUEUED;
+                free(worker);
+                err(udev, "fork of child failed: %m\n");
+                break;
+        default:
+                /* close monitor, but keep address around */
+                udev_monitor_disconnect(worker_monitor);
+                worker->monitor = worker_monitor;
+                worker->pid = pid;
+                worker->state = WORKER_RUNNING;
+                worker->event_start_usec = now_usec();
+                worker->event = event;
+                event->state = EVENT_RUNNING;
+                udev_list_node_append(&worker->node, &worker_list);
+                children++;
+                info(udev, "seq %llu forked new worker [%u]\n", udev_device_get_seqnum(event->dev), pid);
+                break;
+        }
+}
+
+static void event_run(struct event *event)
+{
+        struct udev_list_node *loop;
+
+        udev_list_node_foreach(loop, &worker_list) {
+                struct worker *worker = node_to_worker(loop);
+                ssize_t count;
+
+                if (worker->state != WORKER_IDLE)
+                        continue;
+
+                count = udev_monitor_send_device(monitor, worker->monitor, event->dev);
+                if (count < 0) {
+                        err(event->udev, "worker [%u] did not accept message %zi (%m), kill it\n", worker->pid, count);
+                        kill(worker->pid, SIGKILL);
+                        worker->state = WORKER_KILLED;
+                        continue;
+                }
+                worker_ref(worker);
+                worker->event = event;
+                worker->state = WORKER_RUNNING;
+                worker->event_start_usec = now_usec();
+                event->state = EVENT_RUNNING;
+                return;
+        }
+
+        if (children >= children_max) {
+                if (children_max > 1)
+                        info(event->udev, "maximum number (%i) of children reached\n", children);
+                return;
+        }
+
+        /* start new worker and pass initial device */
+        worker_new(event);
+}
+
+static int event_queue_insert(struct udev_device *dev)
+{
+        struct event *event;
+
+        event = calloc(1, sizeof(struct event));
+        if (event == NULL)
+                return -1;
+
+        event->udev = udev_device_get_udev(dev);
+        event->dev = dev;
+        event->seqnum = udev_device_get_seqnum(dev);
+        event->devpath = udev_device_get_devpath(dev);
+        event->devpath_len = strlen(event->devpath);
+        event->devpath_old = udev_device_get_devpath_old(dev);
+        event->devnum = udev_device_get_devnum(dev);
+        event->is_block = (strcmp("block", udev_device_get_subsystem(dev)) == 0);
+        event->ifindex = udev_device_get_ifindex(dev);
+
+        udev_queue_export_device_queued(udev_queue_export, dev);
+        info(event->udev, "seq %llu queued, '%s' '%s'\n", udev_device_get_seqnum(dev),
+             udev_device_get_action(dev), udev_device_get_subsystem(dev));
+
+        event->state = EVENT_QUEUED;
+        udev_list_node_append(&event->node, &event_list);
+        return 0;
+}
+
+static void worker_kill(struct udev *udev, int retain)
+{
+        struct udev_list_node *loop;
+        int max;
+
+        if (children <= retain)
+                return;
+
+        max = children - retain;
+
+        udev_list_node_foreach(loop, &worker_list) {
+                struct worker *worker = node_to_worker(loop);
+
+                if (max-- <= 0)
+                        break;
+
+                if (worker->state == WORKER_KILLED)
+                        continue;
+
+                worker->state = WORKER_KILLED;
+                kill(worker->pid, SIGTERM);
+        }
+}
+
+/* lookup event for identical, parent, child device */
+static bool is_devpath_busy(struct event *event)
+{
+        struct udev_list_node *loop;
+        size_t common;
+
+        /* check if queue contains events we depend on */
+        udev_list_node_foreach(loop, &event_list) {
+                struct event *loop_event = node_to_event(loop);
+
+                /* we already found a later event, earlier can not block us, no need to check again */
+                if (loop_event->seqnum < event->delaying_seqnum)
+                        continue;
+
+                /* event we checked earlier still exists, no need to check again */
+                if (loop_event->seqnum == event->delaying_seqnum)
+                        return true;
+
+                /* found ourself, no later event can block us */
+                if (loop_event->seqnum >= event->seqnum)
+                        break;
+
+                /* check major/minor */
+                if (major(event->devnum) != 0 && event->devnum == loop_event->devnum && event->is_block == loop_event->is_block)
+                        return true;
+
+                /* check network device ifindex */
+                if (event->ifindex != 0 && event->ifindex == loop_event->ifindex)
+                        return true;
+
+                /* check our old name */
+                if (event->devpath_old != NULL && strcmp(loop_event->devpath, event->devpath_old) == 0) {
+                        event->delaying_seqnum = loop_event->seqnum;
+                        return true;
+                }
+
+                /* compare devpath */
+                common = MIN(loop_event->devpath_len, event->devpath_len);
+
+                /* one devpath is contained in the other? */
+                if (memcmp(loop_event->devpath, event->devpath, common) != 0)
+                        continue;
+
+                /* identical device event found */
+                if (loop_event->devpath_len == event->devpath_len) {
+                        /* devices names might have changed/swapped in the meantime */
+                        if (major(event->devnum) != 0 && (event->devnum != loop_event->devnum || event->is_block != loop_event->is_block))
+                                continue;
+                        if (event->ifindex != 0 && event->ifindex != loop_event->ifindex)
+                                continue;
+                        event->delaying_seqnum = loop_event->seqnum;
+                        return true;
+                }
+
+                /* parent device event found */
+                if (event->devpath[common] == '/') {
+                        event->delaying_seqnum = loop_event->seqnum;
+                        return true;
+                }
+
+                /* child device event found */
+                if (loop_event->devpath[common] == '/') {
+                        event->delaying_seqnum = loop_event->seqnum;
+                        return true;
+                }
+
+                /* no matching device */
+                continue;
+        }
+
+        return false;
+}
+
+static void event_queue_start(struct udev *udev)
+{
+        struct udev_list_node *loop;
+
+        udev_list_node_foreach(loop, &event_list) {
+                struct event *event = node_to_event(loop);
+
+                if (event->state != EVENT_QUEUED)
+                        continue;
+
+                /* do not start event if parent or child event is still running */
+                if (is_devpath_busy(event)) {
+                        dbg(udev, "delay seq %llu (%s)\n", event->seqnum, event->devpath);
+                        continue;
+                }
+
+                event_run(event);
+        }
+}
+
+static void event_queue_cleanup(struct udev *udev, enum event_state match_type)
+{
+        struct udev_list_node *loop, *tmp;
+
+        udev_list_node_foreach_safe(loop, tmp, &event_list) {
+                struct event *event = node_to_event(loop);
+
+                if (match_type != EVENT_UNDEF && match_type != event->state)
+                        continue;
+
+                event_queue_delete(event, false);
+        }
+}
+
+static void worker_returned(int fd_worker)
+{
+        for (;;) {
+                struct worker_message msg;
+                ssize_t size;
+                struct udev_list_node *loop;
+
+                size = recv(fd_worker, &msg, sizeof(struct worker_message), MSG_DONTWAIT);
+                if (size != sizeof(struct worker_message))
+                        break;
+
+                /* lookup worker who sent the signal */
+                udev_list_node_foreach(loop, &worker_list) {
+                        struct worker *worker = node_to_worker(loop);
+
+                        if (worker->pid != msg.pid)
+                                continue;
+
+                        /* worker returned */
+                        if (worker->event) {
+                                worker->event->exitcode = msg.exitcode;
+                                event_queue_delete(worker->event, true);
+                                worker->event = NULL;
+                        }
+                        if (worker->state != WORKER_KILLED)
+                                worker->state = WORKER_IDLE;
+                        worker_unref(worker);
+                        break;
+                }
+        }
+}
+
+/* receive the udevd message from userspace */
+static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl)
+{
+        struct udev *udev = udev_ctrl_get_udev(uctrl);
+        struct udev_ctrl_connection *ctrl_conn;
+        struct udev_ctrl_msg *ctrl_msg = NULL;
+        const char *str;
+        int i;
+
+        ctrl_conn = udev_ctrl_get_connection(uctrl);
+        if (ctrl_conn == NULL)
+                goto out;
+
+        ctrl_msg = udev_ctrl_receive_msg(ctrl_conn);
+        if (ctrl_msg == NULL)
+                goto out;
+
+        i = udev_ctrl_get_set_log_level(ctrl_msg);
+        if (i >= 0) {
+                info(udev, "udevd message (SET_LOG_PRIORITY) received, log_priority=%i\n", i);
+                udev_set_log_priority(udev, i);
+                worker_kill(udev, 0);
+        }
+
+        if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) {
+                info(udev, "udevd message (STOP_EXEC_QUEUE) received\n");
+                stop_exec_queue = true;
+        }
+
+        if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) {
+                info(udev, "udevd message (START_EXEC_QUEUE) received\n");
+                stop_exec_queue = false;
+        }
+
+        if (udev_ctrl_get_reload(ctrl_msg) > 0) {
+                info(udev, "udevd message (RELOAD) received\n");
+                reload = true;
+        }
+
+        str = udev_ctrl_get_set_env(ctrl_msg);
+        if (str != NULL) {
+                char *key;
+
+                key = strdup(str);
+                if (key != NULL) {
+                        char *val;
+
+                        val = strchr(key, '=');
+                        if (val != NULL) {
+                                val[0] = '\0';
+                                val = &val[1];
+                                if (val[0] == '\0') {
+                                        info(udev, "udevd message (ENV) received, unset '%s'\n", key);
+                                        udev_add_property(udev, key, NULL);
+                                } else {
+                                        info(udev, "udevd message (ENV) received, set '%s=%s'\n", key, val);
+                                        udev_add_property(udev, key, val);
+                                }
+                        } else {
+                                err(udev, "wrong key format '%s'\n", key);
+                        }
+                        free(key);
+                }
+                worker_kill(udev, 0);
+        }
+
+        i = udev_ctrl_get_set_children_max(ctrl_msg);
+        if (i >= 0) {
+                info(udev, "udevd message (SET_MAX_CHILDREN) received, children_max=%i\n", i);
+                children_max = i;
+        }
+
+        if (udev_ctrl_get_ping(ctrl_msg) > 0)
+                info(udev, "udevd message (SYNC) received\n");
+
+        if (udev_ctrl_get_exit(ctrl_msg) > 0) {
+                info(udev, "udevd message (EXIT) received\n");
+                udev_exit = true;
+                /* keep reference to block the client until we exit */
+                udev_ctrl_connection_ref(ctrl_conn);
+        }
+out:
+        udev_ctrl_msg_unref(ctrl_msg);
+        return udev_ctrl_connection_unref(ctrl_conn);
+}
+
+/* read inotify messages */
+static int handle_inotify(struct udev *udev)
+{
+        int nbytes, pos;
+        char *buf;
+        struct inotify_event *ev;
+
+        if ((ioctl(fd_inotify, FIONREAD, &nbytes) < 0) || (nbytes <= 0))
+                return 0;
+
+        buf = malloc(nbytes);
+        if (buf == NULL) {
+                err(udev, "error getting buffer for inotify\n");
+                return -1;
+        }
+
+        nbytes = read(fd_inotify, buf, nbytes);
+
+        for (pos = 0; pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) {
+                struct udev_device *dev;
+
+                ev = (struct inotify_event *)(buf + pos);
+                dev = udev_watch_lookup(udev, ev->wd);
+                if (dev != NULL) {
+                        info(udev, "inotify event: %x for %s\n", ev->mask, udev_device_get_devnode(dev));
+                        if (ev->mask & IN_CLOSE_WRITE) {
+                                char filename[UTIL_PATH_SIZE];
+                                int fd;
+
+                                info(udev, "device %s closed, synthesising 'change'\n", udev_device_get_devnode(dev));
+                                util_strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL);
+                                fd = open(filename, O_WRONLY);
+                                if (fd >= 0) {
+                                        if (write(fd, "change", 6) < 0)
+                                                info(udev, "error writing uevent: %m\n");
+                                        close(fd);
+                                }
+                        }
+                        if (ev->mask & IN_IGNORED)
+                                udev_watch_end(udev, dev);
+
+                        udev_device_unref(dev);
+                }
+
+        }
+
+        free(buf);
+        return 0;
+}
+
+static void handle_signal(struct udev *udev, int signo)
+{
+        switch (signo) {
+        case SIGINT:
+        case SIGTERM:
+                udev_exit = true;
+                break;
+        case SIGCHLD:
+                for (;;) {
+                        pid_t pid;
+                        int status;
+                        struct udev_list_node *loop, *tmp;
+
+                        pid = waitpid(-1, &status, WNOHANG);
+                        if (pid <= 0)
+                                break;
+
+                        udev_list_node_foreach_safe(loop, tmp, &worker_list) {
+                                struct worker *worker = node_to_worker(loop);
+
+                                if (worker->pid != pid)
+                                        continue;
+                                info(udev, "worker [%u] exit\n", pid);
+
+                                if (WIFEXITED(status)) {
+                                        if (WEXITSTATUS(status) != 0)
+                                                err(udev, "worker [%u] exit with return code %i\n", pid, WEXITSTATUS(status));
+                                } else if (WIFSIGNALED(status)) {
+                                        err(udev, "worker [%u] terminated by signal %i (%s)\n",
+                                            pid, WTERMSIG(status), strsignal(WTERMSIG(status)));
+                                } else if (WIFSTOPPED(status)) {
+                                        err(udev, "worker [%u] stopped\n", pid);
+                                } else if (WIFCONTINUED(status)) {
+                                        err(udev, "worker [%u] continued\n", pid);
+                                } else {
+                                        err(udev, "worker [%u] exit with status 0x%04x\n", pid, status);
+                                }
+
+                                if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+                                        if (worker->event) {
+                                                err(udev, "worker [%u] failed while handling '%s'\n",
+                                                    pid, worker->event->devpath);
+                                                worker->event->exitcode = -32;
+                                                event_queue_delete(worker->event, true);
+                                                /* drop reference taken for state 'running' */
+                                                worker_unref(worker);
+                                        }
+                                }
+                                worker_unref(worker);
+                                break;
+                        }
+                }
+                break;
+        case SIGHUP:
+                reload = true;
+                break;
+        }
+}
+
+static void static_dev_create_from_modules(struct udev *udev)
+{
+        struct utsname kernel;
+        char modules[UTIL_PATH_SIZE];
+        char buf[4096];
+        FILE *f;
+
+        uname(&kernel);
+        util_strscpyl(modules, sizeof(modules), "/lib/modules/", kernel.release, "/modules.devname", NULL);
+        f = fopen(modules, "r");
+        if (f == NULL)
+                return;
+
+        while (fgets(buf, sizeof(buf), f) != NULL) {
+                char *s;
+                const char *modname;
+                const char *devname;
+                const char *devno;
+                int maj, min;
+                char type;
+                mode_t mode;
+                char filename[UTIL_PATH_SIZE];
+
+                if (buf[0] == '#')
+                        continue;
+
+                modname = buf;
+                s = strchr(modname, ' ');
+                if (s == NULL)
+                        continue;
+                s[0] = '\0';
+
+                devname = &s[1];
+                s = strchr(devname, ' ');
+                if (s == NULL)
+                        continue;
+                s[0] = '\0';
+
+                devno = &s[1];
+                s = strchr(devno, ' ');
+                if (s == NULL)
+                        s = strchr(devno, '\n');
+                if (s != NULL)
+                        s[0] = '\0';
+                if (sscanf(devno, "%c%u:%u", &type, &maj, &min) != 3)
+                        continue;
+
+                if (type == 'c')
+                        mode = S_IFCHR;
+                else if (type == 'b')
+                        mode = S_IFBLK;
+                else
+                        continue;
+
+                util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/", devname, NULL);
+                util_create_path_selinux(udev, filename);
+                udev_selinux_setfscreatecon(udev, filename, mode);
+                info(udev, "mknod '%s' %c%u:%u\n", filename, type, maj, min);
+                if (mknod(filename, mode, makedev(maj, min)) < 0 && errno == EEXIST)
+                        utimensat(AT_FDCWD, filename, NULL, 0);
+                udev_selinux_resetfscreatecon(udev);
+        }
+
+        fclose(f);
+}
+
+static int copy_dev_dir(struct udev *udev, DIR *dir_from, DIR *dir_to, int maxdepth)
+{
+        struct dirent *dent;
+
+        for (dent = readdir(dir_from); dent != NULL; dent = readdir(dir_from)) {
+                struct stat stats;
+
+                if (dent->d_name[0] == '.')
+                        continue;
+                if (fstatat(dirfd(dir_from), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
+                        continue;
+
+                if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) {
+                        udev_selinux_setfscreateconat(udev, dirfd(dir_to), dent->d_name, stats.st_mode & 0777);
+                        if (mknodat(dirfd(dir_to), dent->d_name, stats.st_mode, stats.st_rdev) == 0) {
+                                fchmodat(dirfd(dir_to), dent->d_name, stats.st_mode & 0777, 0);
+                                fchownat(dirfd(dir_to), dent->d_name, stats.st_uid, stats.st_gid, 0);
+                        } else {
+                                utimensat(dirfd(dir_to), dent->d_name, NULL, 0);
+                        }
+                        udev_selinux_resetfscreatecon(udev);
+                } else if (S_ISLNK(stats.st_mode)) {
+                        char target[UTIL_PATH_SIZE];
+                        ssize_t len;
+
+                        len = readlinkat(dirfd(dir_from), dent->d_name, target, sizeof(target));
+                        if (len <= 0 || len == (ssize_t)sizeof(target))
+                                continue;
+                        target[len] = '\0';
+                        udev_selinux_setfscreateconat(udev, dirfd(dir_to), dent->d_name, S_IFLNK);
+                        if (symlinkat(target, dirfd(dir_to), dent->d_name) < 0 && errno == EEXIST)
+                                utimensat(dirfd(dir_to), dent->d_name, NULL, AT_SYMLINK_NOFOLLOW);
+                        udev_selinux_resetfscreatecon(udev);
+                } else if (S_ISDIR(stats.st_mode)) {
+                        DIR *dir2_from, *dir2_to;
+
+                        if (maxdepth == 0)
+                                continue;
+
+                        udev_selinux_setfscreateconat(udev, dirfd(dir_to), dent->d_name, S_IFDIR|0755);
+                        mkdirat(dirfd(dir_to), dent->d_name, 0755);
+                        udev_selinux_resetfscreatecon(udev);
+
+                        dir2_to = fdopendir(openat(dirfd(dir_to), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+                        if (dir2_to == NULL)
+                                continue;
+
+                        dir2_from = fdopendir(openat(dirfd(dir_from), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+                        if (dir2_from == NULL) {
+                                closedir(dir2_to);
+                                continue;
+                        }
+
+                        copy_dev_dir(udev, dir2_from, dir2_to, maxdepth-1);
+
+                        closedir(dir2_to);
+                        closedir(dir2_from);
+                }
+        }
+
+        return 0;
+}
+
+static void static_dev_create_links(struct udev *udev, DIR *dir)
+{
+        struct stdlinks {
+                const char *link;
+                const char *target;
+        };
+        static const struct stdlinks stdlinks[] = {
+                { "core", "/proc/kcore" },
+                { "fd", "/proc/self/fd" },
+                { "stdin", "/proc/self/fd/0" },
+                { "stdout", "/proc/self/fd/1" },
+                { "stderr", "/proc/self/fd/2" },
+        };
+        unsigned int i;
+
+        for (i = 0; i < ARRAY_SIZE(stdlinks); i++) {
+                struct stat sb;
+
+                if (stat(stdlinks[i].target, &sb) == 0) {
+                        udev_selinux_setfscreateconat(udev, dirfd(dir), stdlinks[i].link, S_IFLNK);
+                        if (symlinkat(stdlinks[i].target, dirfd(dir), stdlinks[i].link) < 0 && errno == EEXIST)
+                                utimensat(dirfd(dir), stdlinks[i].link, NULL, AT_SYMLINK_NOFOLLOW);
+                        udev_selinux_resetfscreatecon(udev);
+                }
+        }
+}
+
+static void static_dev_create_from_devices(struct udev *udev, DIR *dir)
+{
+        DIR *dir_from;
+
+        dir_from = opendir(PKGLIBEXECDIR "/devices");
+        if (dir_from == NULL)
+                return;
+        copy_dev_dir(udev, dir_from, dir, 8);
+        closedir(dir_from);
+}
+
+static void static_dev_create(struct udev *udev)
+{
+        DIR *dir;
+
+        dir = opendir(udev_get_dev_path(udev));
+        if (dir == NULL)
+                return;
+
+        static_dev_create_links(udev, dir);
+        static_dev_create_from_devices(udev, dir);
+
+        closedir(dir);
+}
+
+static int mem_size_mb(void)
+{
+        FILE *f;
+        char buf[4096];
+        long int memsize = -1;
+
+        f = fopen("/proc/meminfo", "r");
+        if (f == NULL)
+                return -1;
+
+        while (fgets(buf, sizeof(buf), f) != NULL) {
+                long int value;
+
+                if (sscanf(buf, "MemTotal: %ld kB", &value) == 1) {
+                        memsize = value / 1024;
+                        break;
+                }
+        }
+
+        fclose(f);
+        return memsize;
+}
+
+static int convert_db(struct udev *udev)
+{
+        char filename[UTIL_PATH_SIZE];
+        FILE *f;
+        struct udev_enumerate *udev_enumerate;
+        struct udev_list_entry *list_entry;
+
+        /* current database */
+        util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/data", NULL);
+        if (access(filename, F_OK) >= 0)
+                return 0;
+
+        /* make sure we do not get here again */
+        util_create_path(udev, filename);
+        mkdir(filename, 0755);
+
+        /* old database */
+        util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/db", NULL);
+        if (access(filename, F_OK) < 0)
+                return 0;
+
+        f = fopen("/dev/kmsg", "w");
+        if (f != NULL) {
+                fprintf(f, "<30>udevd[%u]: converting old udev database\n", getpid());
+                fclose(f);
+        }
+
+        udev_enumerate = udev_enumerate_new(udev);
+        if (udev_enumerate == NULL)
+                return -1;
+        udev_enumerate_scan_devices(udev_enumerate);
+        udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(udev_enumerate)) {
+                struct udev_device *device;
+
+                device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(list_entry));
+                if (device == NULL)
+                        continue;
+
+                /* try to find the old database for devices without a current one */
+                if (udev_device_read_db(device, NULL) < 0) {
+                        bool have_db;
+                        const char *id;
+                        struct stat stats;
+                        char devpath[UTIL_PATH_SIZE];
+                        char from[UTIL_PATH_SIZE];
+
+                        have_db = false;
+
+                        /* find database in old location */
+                        id = udev_device_get_id_filename(device);
+                        util_strscpyl(from, sizeof(from), udev_get_dev_path(udev), "/.udev/db/", id, NULL);
+                        if (lstat(from, &stats) == 0) {
+                                if (!have_db) {
+                                        udev_device_read_db(device, from);
+                                        have_db = true;
+                                }
+                                unlink(from);
+                        }
+
+                        /* find old database with $subsys:$sysname name */
+                        util_strscpyl(from, sizeof(from), udev_get_dev_path(udev),
+                                     "/.udev/db/", udev_device_get_subsystem(device), ":",
+                                     udev_device_get_sysname(device), NULL);
+                        if (lstat(from, &stats) == 0) {
+                                if (!have_db) {
+                                        udev_device_read_db(device, from);
+                                        have_db = true;
+                                }
+                                unlink(from);
+                        }
+
+                        /* find old database with the encoded devpath name */
+                        util_path_encode(udev_device_get_devpath(device), devpath, sizeof(devpath));
+                        util_strscpyl(from, sizeof(from), udev_get_dev_path(udev), "/.udev/db/", devpath, NULL);
+                        if (lstat(from, &stats) == 0) {
+                                if (!have_db) {
+                                        udev_device_read_db(device, from);
+                                        have_db = true;
+                                }
+                                unlink(from);
+                        }
+
+                        /* write out new database */
+                        if (have_db)
+                                udev_device_update_db(device);
+                }
+                udev_device_unref(device);
+        }
+        udev_enumerate_unref(udev_enumerate);
+        return 0;
+}
+
+static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink)
+{
+        int ctrl = -1, netlink = -1;
+        int fd, n;
+
+        n = sd_listen_fds(true);
+        if (n <= 0)
+                return -1;
+
+        for (fd = SD_LISTEN_FDS_START; fd < n + SD_LISTEN_FDS_START; fd++) {
+                if (sd_is_socket(fd, AF_LOCAL, SOCK_SEQPACKET, -1)) {
+                        if (ctrl >= 0)
+                                return -1;
+                        ctrl = fd;
+                        continue;
+                }
+
+                if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1)) {
+                        if (netlink >= 0)
+                                return -1;
+                        netlink = fd;
+                        continue;
+                }
+
+                return -1;
+        }
+
+        if (ctrl < 0 || netlink < 0)
+                return -1;
+
+        info(udev, "ctrl=%i netlink=%i\n", ctrl, netlink);
+        *rctrl = ctrl;
+        *rnetlink = netlink;
+        return 0;
+}
+
+static bool check_rules_timestamp(struct udev *udev)
+{
+        char **p;
+        unsigned long long *stamp_usec;
+        int i, n;
+        bool changed = false;
+
+        n = udev_get_rules_path(udev, &p, &stamp_usec);
+        for (i = 0; i < n; i++) {
+                struct stat stats;
+
+                if (stat(p[i], &stats) < 0)
+                        continue;
+
+                if (stamp_usec[i] == ts_usec(&stats.st_mtim))
+                        continue;
+
+                /* first check */
+                if (stamp_usec[i] != 0) {
+                        info(udev, "reload - timestamp of '%s' changed\n", p[i]);
+                        changed = true;
+                }
+
+                /* update timestamp */
+                stamp_usec[i] = ts_usec(&stats.st_mtim);
+        }
+
+        return changed;
+}
+
+int main(int argc, char *argv[])
+{
+        struct udev *udev;
+        FILE *f;
+        sigset_t mask;
+        int daemonize = false;
+        int resolve_names = 1;
+        static const struct option options[] = {
+                { "daemon", no_argument, NULL, 'd' },
+                { "debug", no_argument, NULL, 'D' },
+                { "children-max", required_argument, NULL, 'c' },
+                { "exec-delay", required_argument, NULL, 'e' },
+                { "resolve-names", required_argument, NULL, 'N' },
+                { "help", no_argument, NULL, 'h' },
+                { "version", no_argument, NULL, 'V' },
+                {}
+        };
+        int fd_ctrl = -1;
+        int fd_netlink = -1;
+        int fd_worker = -1;
+        struct epoll_event ep_ctrl, ep_inotify, ep_signal, ep_netlink, ep_worker;
+        struct udev_ctrl_connection *ctrl_conn = NULL;
+        char **s;
+        int rc = 1;
+
+        udev = udev_new();
+        if (udev == NULL)
+                goto exit;
+
+        udev_log_init("udevd");
+        udev_set_log_fn(udev, udev_main_log);
+        info(udev, "version %s\n", VERSION);
+        udev_selinux_init(udev);
+
+        for (;;) {
+                int option;
+
+                option = getopt_long(argc, argv, "c:deDtN:hV", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'd':
+                        daemonize = true;
+                        break;
+                case 'c':
+                        children_max = strtoul(optarg, NULL, 0);
+                        break;
+                case 'e':
+                        exec_delay = strtoul(optarg, NULL, 0);
+                        break;
+                case 'D':
+                        debug = true;
+                        if (udev_get_log_priority(udev) < LOG_INFO)
+                                udev_set_log_priority(udev, LOG_INFO);
+                        break;
+                case 'N':
+                        if (strcmp (optarg, "early") == 0) {
+                                resolve_names = 1;
+                        } else if (strcmp (optarg, "late") == 0) {
+                                resolve_names = 0;
+                        } else if (strcmp (optarg, "never") == 0) {
+                                resolve_names = -1;
+                        } else {
+                                fprintf(stderr, "resolve-names must be early, late or never\n");
+                                err(udev, "resolve-names must be early, late or never\n");
+                                goto exit;
+                        }
+                        break;
+                case 'h':
+                        printf("Usage: udevd OPTIONS\n"
+                               "  --daemon\n"
+                               "  --debug\n"
+                               "  --children-max=<maximum number of workers>\n"
+                               "  --exec-delay=<seconds to wait before executing RUN=>\n"
+                               "  --resolve-names=early|late|never\n"
+                               "  --version\n"
+                               "  --help\n"
+                               "\n");
+                        goto exit;
+                case 'V':
+                        printf("%s\n", VERSION);
+                        goto exit;
+                default:
+                        goto exit;
+                }
+        }
+
+        /*
+         * read the kernel commandline, in case we need to get into debug mode
+         *   udev.log-priority=<level>              syslog priority
+         *   udev.children-max=<number of workers>  events are fully serialized if set to 1
+         *
+         */
+        f = fopen("/proc/cmdline", "r");
+        if (f != NULL) {
+                char cmdline[4096];
+
+                if (fgets(cmdline, sizeof(cmdline), f) != NULL) {
+                        char *pos;
+
+                        pos = strstr(cmdline, "udev.log-priority=");
+                        if (pos != NULL) {
+                                pos += strlen("udev.log-priority=");
+                                udev_set_log_priority(udev, util_log_priority(pos));
+                        }
+
+                        pos = strstr(cmdline, "udev.children-max=");
+                        if (pos != NULL) {
+                                pos += strlen("udev.children-max=");
+                                children_max = strtoul(pos, NULL, 0);
+                        }
+
+                        pos = strstr(cmdline, "udev.exec-delay=");
+                        if (pos != NULL) {
+                                pos += strlen("udev.exec-delay=");
+                                exec_delay = strtoul(pos, NULL, 0);
+                        }
+                }
+                fclose(f);
+        }
+
+        if (getuid() != 0) {
+                fprintf(stderr, "root privileges required\n");
+                err(udev, "root privileges required\n");
+                goto exit;
+        }
+
+        /* set umask before creating any file/directory */
+        chdir("/");
+        umask(022);
+
+        /* /run/udev */
+        mkdir(udev_get_run_path(udev), 0755);
+
+        /* create standard links, copy static nodes, create nodes from modules */
+        static_dev_create(udev);
+        static_dev_create_from_modules(udev);
+
+        /* before opening new files, make sure std{in,out,err} fds are in a sane state */
+        if (daemonize) {
+                int fd;
+
+                fd = open("/dev/null", O_RDWR);
+                if (fd >= 0) {
+                        if (write(STDOUT_FILENO, 0, 0) < 0)
+                                dup2(fd, STDOUT_FILENO);
+                        if (write(STDERR_FILENO, 0, 0) < 0)
+                                dup2(fd, STDERR_FILENO);
+                        if (fd > STDERR_FILENO)
+                                close(fd);
+                } else {
+                        fprintf(stderr, "cannot open /dev/null\n");
+                        err(udev, "cannot open /dev/null\n");
+                }
+        }
+
+        if (systemd_fds(udev, &fd_ctrl, &fd_netlink) >= 0) {
+                /* get control and netlink socket from from systemd */
+                udev_ctrl = udev_ctrl_new_from_fd(udev, fd_ctrl);
+                if (udev_ctrl == NULL) {
+                        err(udev, "error taking over udev control socket");
+                        rc = 1;
+                        goto exit;
+                }
+
+                monitor = udev_monitor_new_from_netlink_fd(udev, "kernel", fd_netlink);
+                if (monitor == NULL) {
+                        err(udev, "error taking over netlink socket\n");
+                        rc = 3;
+                        goto exit;
+                }
+        } else {
+                /* open control and netlink socket */
+                udev_ctrl = udev_ctrl_new(udev);
+                if (udev_ctrl == NULL) {
+                        fprintf(stderr, "error initializing udev control socket");
+                        err(udev, "error initializing udev control socket");
+                        rc = 1;
+                        goto exit;
+                }
+                fd_ctrl = udev_ctrl_get_fd(udev_ctrl);
+
+                monitor = udev_monitor_new_from_netlink(udev, "kernel");
+                if (monitor == NULL) {
+                        fprintf(stderr, "error initializing netlink socket\n");
+                        err(udev, "error initializing netlink socket\n");
+                        rc = 3;
+                        goto exit;
+                }
+                fd_netlink = udev_monitor_get_fd(monitor);
+        }
+
+        if (udev_monitor_enable_receiving(monitor) < 0) {
+                fprintf(stderr, "error binding netlink socket\n");
+                err(udev, "error binding netlink socket\n");
+                rc = 3;
+                goto exit;
+        }
+
+        if (udev_ctrl_enable_receiving(udev_ctrl) < 0) {
+                fprintf(stderr, "error binding udev control socket\n");
+                err(udev, "error binding udev control socket\n");
+                rc = 1;
+                goto exit;
+        }
+
+        udev_monitor_set_receive_buffer_size(monitor, 128*1024*1024);
+
+        /* create queue file before signalling 'ready', to make sure we block 'settle' */
+        udev_queue_export = udev_queue_export_new(udev);
+        if (udev_queue_export == NULL) {
+                err(udev, "error creating queue file\n");
+                goto exit;
+        }
+
+        if (daemonize) {
+                pid_t pid;
+                int fd;
+
+                pid = fork();
+                switch (pid) {
+                case 0:
+                        break;
+                case -1:
+                        err(udev, "fork of daemon failed: %m\n");
+                        rc = 4;
+                        goto exit;
+                default:
+                        rc = EXIT_SUCCESS;
+                        goto exit_daemonize;
+                }
+
+                setsid();
+
+                fd = open("/proc/self/oom_score_adj", O_RDWR);
+                if (fd < 0) {
+                        /* Fallback to old interface */
+                        fd = open("/proc/self/oom_adj", O_RDWR);
+                        if (fd < 0) {
+                                err(udev, "error disabling OOM: %m\n");
+                        } else {
+                                /* OOM_DISABLE == -17 */
+                                write(fd, "-17", 3);
+                                close(fd);
+                        }
+                } else {
+                        write(fd, "-1000", 5);
+                        close(fd);
+                }
+        } else {
+                sd_notify(1, "READY=1");
+        }
+
+        f = fopen("/dev/kmsg", "w");
+        if (f != NULL) {
+                fprintf(f, "<30>udevd[%u]: starting version " VERSION "\n", getpid());
+                fclose(f);
+        }
+
+        if (!debug) {
+                int fd;
+
+                fd = open("/dev/null", O_RDWR);
+                if (fd >= 0) {
+                        dup2(fd, STDIN_FILENO);
+                        dup2(fd, STDOUT_FILENO);
+                        dup2(fd, STDERR_FILENO);
+                        close(fd);
+                }
+        }
+
+        fd_inotify = udev_watch_init(udev);
+        if (fd_inotify < 0) {
+                fprintf(stderr, "error initializing inotify\n");
+                err(udev, "error initializing inotify\n");
+                rc = 4;
+                goto exit;
+        }
+        udev_watch_restore(udev);
+
+        /* block and listen to all signals on signalfd */
+        sigfillset(&mask);
+        sigprocmask(SIG_SETMASK, &mask, &sigmask_orig);
+        fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+        if (fd_signal < 0) {
+                fprintf(stderr, "error creating signalfd\n");
+                err(udev, "error creating signalfd\n");
+                rc = 5;
+                goto exit;
+        }
+
+        /* unnamed socket from workers to the main daemon */
+        if (socketpair(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0, worker_watch) < 0) {
+                fprintf(stderr, "error creating socketpair\n");
+                err(udev, "error creating socketpair\n");
+                rc = 6;
+                goto exit;
+        }
+        fd_worker = worker_watch[READ_END];
+
+        udev_builtin_init(udev);
+
+        rules = udev_rules_new(udev, resolve_names);
+        if (rules == NULL) {
+                err(udev, "error reading rules\n");
+                goto exit;
+        }
+
+        memset(&ep_ctrl, 0, sizeof(struct epoll_event));
+        ep_ctrl.events = EPOLLIN;
+        ep_ctrl.data.fd = fd_ctrl;
+
+        memset(&ep_inotify, 0, sizeof(struct epoll_event));
+        ep_inotify.events = EPOLLIN;
+        ep_inotify.data.fd = fd_inotify;
+
+        memset(&ep_signal, 0, sizeof(struct epoll_event));
+        ep_signal.events = EPOLLIN;
+        ep_signal.data.fd = fd_signal;
+
+        memset(&ep_netlink, 0, sizeof(struct epoll_event));
+        ep_netlink.events = EPOLLIN;
+        ep_netlink.data.fd = fd_netlink;
+
+        memset(&ep_worker, 0, sizeof(struct epoll_event));
+        ep_worker.events = EPOLLIN;
+        ep_worker.data.fd = fd_worker;
+
+        fd_ep = epoll_create1(EPOLL_CLOEXEC);
+        if (fd_ep < 0) {
+                err(udev, "error creating epoll fd: %m\n");
+                goto exit;
+        }
+        if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_ctrl, &ep_ctrl) < 0 ||
+            epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_inotify, &ep_inotify) < 0 ||
+            epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 ||
+            epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_netlink, &ep_netlink) < 0 ||
+            epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_worker, &ep_worker) < 0) {
+                err(udev, "fail to add fds to epoll: %m\n");
+                goto exit;
+        }
+
+        /* if needed, convert old database from earlier udev version */
+        convert_db(udev);
+
+        if (children_max <= 0) {
+                int memsize = mem_size_mb();
+
+                /* set value depending on the amount of RAM */
+                if (memsize > 0)
+                        children_max = 128 + (memsize / 8);
+                else
+                        children_max = 128;
+        }
+        info(udev, "set children_max to %u\n", children_max);
+
+        udev_rules_apply_static_dev_perms(rules);
+
+        udev_list_node_init(&event_list);
+        udev_list_node_init(&worker_list);
+
+        for (;;) {
+                static unsigned long long last_usec;
+                struct epoll_event ev[8];
+                int fdcount;
+                int timeout;
+                bool is_worker, is_signal, is_inotify, is_netlink, is_ctrl;
+                int i;
+
+                if (udev_exit) {
+                        /* close sources of new events and discard buffered events */
+                        if (fd_ctrl >= 0) {
+                                epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_ctrl, NULL);
+                                fd_ctrl = -1;
+                        }
+                        if (monitor != NULL) {
+                                epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_netlink, NULL);
+                                udev_monitor_unref(monitor);
+                                monitor = NULL;
+                        }
+                        if (fd_inotify >= 0) {
+                                epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_inotify, NULL);
+                                close(fd_inotify);
+                                fd_inotify = -1;
+                        }
+
+                        /* discard queued events and kill workers */
+                        event_queue_cleanup(udev, EVENT_QUEUED);
+                        worker_kill(udev, 0);
+
+                        /* exit after all has cleaned up */
+                        if (udev_list_node_is_empty(&event_list) && udev_list_node_is_empty(&worker_list))
+                                break;
+
+                        /* timeout at exit for workers to finish */
+                        timeout = 30 * 1000;
+                } else if (udev_list_node_is_empty(&event_list) && children <= 2) {
+                        /* we are idle */
+                        timeout = -1;
+                } else {
+                        /* kill idle or hanging workers */
+                        timeout = 3 * 1000;
+                }
+                fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), timeout);
+                if (fdcount < 0)
+                        continue;
+
+                if (fdcount == 0) {
+                        struct udev_list_node *loop;
+
+                        /* timeout */
+                        if (udev_exit) {
+                                err(udev, "timeout, giving up waiting for workers to finish\n");
+                                break;
+                        }
+
+                        /* kill idle workers */
+                        if (udev_list_node_is_empty(&event_list)) {
+                                info(udev, "cleanup idle workers\n");
+                                worker_kill(udev, 2);
+                        }
+
+                        /* check for hanging events */
+                        udev_list_node_foreach(loop, &worker_list) {
+                                struct worker *worker = node_to_worker(loop);
+
+                                if (worker->state != WORKER_RUNNING)
+                                        continue;
+
+                                if ((now_usec() - worker->event_start_usec) > 30 * 1000 * 1000) {
+                                        err(udev, "worker [%u] timeout, kill it\n", worker->pid,
+                                            worker->event ? worker->event->devpath : "<idle>");
+                                        kill(worker->pid, SIGKILL);
+                                        worker->state = WORKER_KILLED;
+                                        /* drop reference taken for state 'running' */
+                                        worker_unref(worker);
+                                        if (worker->event) {
+                                                err(udev, "seq %llu '%s' killed\n",
+                                                    udev_device_get_seqnum(worker->event->dev), worker->event->devpath);
+                                                worker->event->exitcode = -64;
+                                                event_queue_delete(worker->event, true);
+                                                worker->event = NULL;
+                                        }
+                                }
+                        }
+
+                }
+
+                is_worker = is_signal = is_inotify = is_netlink = is_ctrl = false;
+                for (i = 0; i < fdcount; i++) {
+                        if (ev[i].data.fd == fd_worker && ev[i].events & EPOLLIN)
+                                is_worker = true;
+                        else if (ev[i].data.fd == fd_netlink && ev[i].events & EPOLLIN)
+                                is_netlink = true;
+                        else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN)
+                                is_signal = true;
+                        else if (ev[i].data.fd == fd_inotify && ev[i].events & EPOLLIN)
+                                is_inotify = true;
+                        else if (ev[i].data.fd == fd_ctrl && ev[i].events & EPOLLIN)
+                                is_ctrl = true;
+                }
+
+                /* check for changed config, every 3 seconds at most */
+                if ((now_usec() - last_usec) > 3 * 1000 * 1000) {
+                        if (check_rules_timestamp(udev))
+                                reload = true;
+                        if (udev_builtin_validate(udev))
+                                reload = true;
+
+                        last_usec = now_usec();
+                }
+
+                /* reload requested, HUP signal received, rules changed, builtin changed */
+                if (reload) {
+                        worker_kill(udev, 0);
+                        rules = udev_rules_unref(rules);
+                        udev_builtin_exit(udev);
+                        reload = 0;
+                }
+
+                /* event has finished */
+                if (is_worker)
+                        worker_returned(fd_worker);
+
+                if (is_netlink) {
+                        struct udev_device *dev;
+
+                        dev = udev_monitor_receive_device(monitor);
+                        if (dev != NULL) {
+                                udev_device_set_usec_initialized(dev, now_usec());
+                                if (event_queue_insert(dev) < 0)
+                                        udev_device_unref(dev);
+                        }
+                }
+
+                /* start new events */
+                if (!udev_list_node_is_empty(&event_list) && !udev_exit && !stop_exec_queue) {
+                        if (rules == NULL)
+                                rules = udev_rules_new(udev, resolve_names);
+                        if (rules != NULL)
+                                event_queue_start(udev);
+                }
+
+                if (is_signal) {
+                        struct signalfd_siginfo fdsi;
+                        ssize_t size;
+
+                        size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo));
+                        if (size == sizeof(struct signalfd_siginfo))
+                                handle_signal(udev, fdsi.ssi_signo);
+                }
+
+                /* we are shutting down, the events below are not handled anymore */
+                if (udev_exit)
+                        continue;
+
+                /* device node watch */
+                if (is_inotify)
+                        handle_inotify(udev);
+
+                /*
+                 * This needs to be after the inotify handling, to make sure,
+                 * that the ping is send back after the possibly generated
+                 * "change" events by the inotify device node watch.
+                 *
+                 * A single time we may receive a client connection which we need to
+                 * keep open to block the client. It will be closed right before we
+                 * exit.
+                 */
+                if (is_ctrl)
+                        ctrl_conn = handle_ctrl_msg(udev_ctrl);
+        }
+
+        rc = EXIT_SUCCESS;
+exit:
+        udev_queue_export_cleanup(udev_queue_export);
+        udev_ctrl_cleanup(udev_ctrl);
+exit_daemonize:
+        if (fd_ep >= 0)
+                close(fd_ep);
+        worker_list_cleanup(udev);
+        event_queue_cleanup(udev, EVENT_UNDEF);
+        udev_rules_unref(rules);
+        udev_builtin_exit(udev);
+        if (fd_signal >= 0)
+                close(fd_signal);
+        if (worker_watch[READ_END] >= 0)
+                close(worker_watch[READ_END]);
+        if (worker_watch[WRITE_END] >= 0)
+                close(worker_watch[WRITE_END]);
+        udev_monitor_unref(monitor);
+        udev_queue_export_unref(udev_queue_export);
+        udev_ctrl_connection_unref(ctrl_conn);
+        udev_ctrl_unref(udev_ctrl);
+        udev_selinux_exit(udev);
+        udev_unref(udev);
+        udev_log_close();
+        return rc;
+}
diff --git a/src/udevd.xml b/src/udevd.xml
new file mode 100644
index 0000000..c516eb9
--- /dev/null
+++ b/src/udevd.xml
@@ -0,0 +1,151 @@
+<?xml version='1.0'?>
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="udevd">
+  <refentryinfo>
+    <title>udevd</title>
+    <productname>udev</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>udevd</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo class="version"></refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>udevd</refname><refpurpose>event managing daemon</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>udevd</command>
+      <arg><option>--daemon</option></arg>
+      <arg><option>--debug</option></arg>
+      <arg><option>--children-max=</option></arg>
+      <arg><option>--exec-delay=</option></arg>
+      <arg><option>--resolve-names=early|late|never</option></arg>
+      <arg><option>--version</option></arg>
+      <arg><option>--help</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1><title>Description</title>
+    <para>udevd listens to kernel uevents. For every event, udevd executes matching
+    instructions specified in udev rules. See <citerefentry>
+        <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+      </citerefentry>.</para>
+    <para>On startup the content of the directory <filename>/usr/lib/udev/devices</filename>
+    is copied to <filename>/dev</filename>. If kernel modules specify static device
+    nodes, these nodes are created even without a corresponding kernel device, to
+    allow on-demand loading of kernel modules. Matching permissions specified in udev
+    rules are applied to these static device nodes.</para>
+    <para>The behavior of the running daemon can be changed with
+    <command>udevadm control</command>.</para>
+  </refsect1>
+
+  <refsect1><title>Options</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>--daemon</option></term>
+        <listitem>
+          <para>Detach and run in the background.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--debug</option></term>
+        <listitem>
+          <para>Print debug messages to stderr.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--children-max=</option></term>
+        <listitem>
+          <para>Limit the number of parallel executed events.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--exec-delay=</option></term>
+        <listitem>
+          <para>Number of seconds to delay the execution of RUN instructions.
+          This might be useful when debugging system crashes during coldplug
+          cause by loading non-working kernel modules.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--resolve-names=</option></term>
+        <listitem>
+          <para>Specify when udevd should resolve names of users and groups.
+          When set to <option>early</option> (the default) names will be
+          resolved when the rules are parsed.  When set to
+          <option>late</option> names will be resolved for every event.
+          When set to <option>never</option> names will never be resolved
+          and all devices will be owned by root.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--version</option></term>
+        <listitem>
+          <para>Print version number.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--help</option></term>
+        <listitem>
+          <para>Print help text.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1><title>Environment</title>
+    <variablelist>
+      <varlistentry>
+        <term><varname>UDEV_LOG=</varname></term>
+        <listitem>
+          <para>Set the logging priority.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+ </refsect1>
+
+  <refsect1><title>Kernel command line</title>
+    <variablelist>
+      <varlistentry>
+        <term><varname>udev.log-priority=</varname></term>
+        <listitem>
+          <para>Set the logging priority.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>udev.children-max=</varname></term>
+        <listitem>
+          <para>Limit the number of parallel executed events.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>udev.exec-delay=</varname></term>
+        <listitem>
+          <para>Number of seconds to delay the execution of RUN instructions.
+          This might be useful when debugging system crashes during coldplug
+          cause by loading non-working kernel modules.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+ </refsect1>
+
+  <refsect1><title>Author</title>
+    <para>Written by Kay Sievers <email>kay.sievers@vrfy.org</email>.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry>
+        <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+      </citerefentry>, <citerefentry>
+        <refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum>
+    </citerefentry></para>
+  </refsect1>
+</refentry>
diff --git a/lib/udev/rules.d/60-persistent-v4l.rules b/src/v4l_id/60-persistent-v4l.rules
similarity index 95%
rename from lib/udev/rules.d/60-persistent-v4l.rules
rename to src/v4l_id/60-persistent-v4l.rules
index edfd844..93c5ee8 100644
--- a/lib/udev/rules.d/60-persistent-v4l.rules
+++ b/src/v4l_id/60-persistent-v4l.rules
@@ -4,7 +4,7 @@
 SUBSYSTEM!="video4linux", GOTO="persistent_v4l_end"
 ENV{MAJOR}=="", GOTO="persistent_v4l_end"
 
-IMPORT{program}="v4l_id $tempnode"
+IMPORT{program}="v4l_id $devnode"
 
 SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
 KERNEL=="video*", ENV{ID_SERIAL}=="?*", SYMLINK+="v4l/by-id/$env{ID_BUS}-$env{ID_SERIAL}-video-index$attr{index}"
diff --git a/src/v4l_id/v4l_id.c b/src/v4l_id/v4l_id.c
new file mode 100644
index 0000000..a2a80b5
--- /dev/null
+++ b/src/v4l_id/v4l_id.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (c) 2009 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details:
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <linux/videodev2.h>
+
+int main (int argc, char *argv[])
+{
+        static const struct option options[] = {
+                { "help", no_argument, NULL, 'h' },
+                {}
+        };
+        int fd;
+        char *device;
+        struct v4l2_capability v2cap;
+
+        while (1) {
+                int option;
+
+                option = getopt_long(argc, argv, "h", options, NULL);
+                if (option == -1)
+                        break;
+
+                switch (option) {
+                case 'h':
+                        printf("Usage: v4l_id [--help] <device file>\n\n");
+                        return 0;
+                default:
+                        return 1;
+                }
+        }
+        device = argv[optind];
+
+        if (device == NULL)
+                return 2;
+        fd = open (device, O_RDONLY);
+        if (fd < 0)
+                return 3;
+
+        if (ioctl (fd, VIDIOC_QUERYCAP, &v2cap) == 0) {
+                printf("ID_V4L_VERSION=2\n");
+                printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
+                printf("ID_V4L_CAPABILITIES=:");
+                if ((v2cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) > 0)
+                        printf("capture:");
+                if ((v2cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) > 0)
+                        printf("video_output:");
+                if ((v2cap.capabilities & V4L2_CAP_VIDEO_OVERLAY) > 0)
+                        printf("video_overlay:");
+                if ((v2cap.capabilities & V4L2_CAP_AUDIO) > 0)
+                        printf("audio:");
+                if ((v2cap.capabilities & V4L2_CAP_TUNER) > 0)
+                        printf("tuner:");
+                if ((v2cap.capabilities & V4L2_CAP_RADIO) > 0)
+                        printf("radio:");
+                printf("\n");
+        }
+
+        close (fd);
+        return 0;
+}
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..98fa886
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+/sys
diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py
new file mode 100755
index 0000000..ff1b63d
--- /dev/null
+++ b/test/rule-syntax-check.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+# Simple udev rules syntax checker
+#
+# (C) 2010 Canonical Ltd.
+# Author: Martin Pitt <martin.pitt@ubuntu.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+import re
+import sys
+
+if len(sys.argv) < 2:
+    print >> sys.stderr, 'Usage: %s <rules file> [...]' % sys.argv[0]
+    sys.exit(2)
+
+no_args_tests = re.compile('(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|RESULT|TEST)\s*(?:=|!)=\s*"([^"]*)"$')
+args_tests = re.compile('(ATTRS?|ENV|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*"([^"]*)"$')
+no_args_assign = re.compile('(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|PROGRAM|RUN|LABEL|GOTO|WAIT_FOR|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*"([^"]*)"$')
+args_assign = re.compile('(ATTR|ENV|IMPORT){([a-zA-Z0-9/_.*%-]+)}\s*=\s*"([^"]*)"$')
+
+result = 0
+buffer = ''
+for path in sys.argv[1:]:
+    lineno = 0
+    for line in open(path):
+        lineno += 1
+
+        # handle line continuation
+        if line.endswith('\\\n'):
+            buffer += line[:-2]
+            continue
+        else:
+            line = buffer + line
+            buffer = ''
+
+        # filter out comments and empty lines
+        line = line.strip()
+        if not line or line.startswith('#'):
+            continue
+
+        for clause in line.split(','):
+            clause = clause.strip()
+            if not (no_args_tests.match(clause) or args_tests.match(clause) or
+                    no_args_assign.match(clause) or args_assign.match(clause)):
+
+                print('Invalid line %s:%i: %s' % (path, lineno, line))
+                print('  clause:', clause)
+                print()
+                result = 1
+                break
+
+sys.exit(result)
diff --git a/test/rules-test.sh b/test/rules-test.sh
new file mode 100755
index 0000000..1e224ff
--- /dev/null
+++ b/test/rules-test.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# Call the udev rule syntax checker on all rules that we ship
+#
+# (C) 2010 Canonical Ltd.
+# Author: Martin Pitt <martin.pitt@ubuntu.com>
+
+[ -n "$srcdir" ] || srcdir=`dirname $0`/..
+
+# skip if we don't have python
+type python >/dev/null 2>&1 || {
+        echo "$0: No python installed, skipping udev rule syntax check"
+        exit 0
+}
+
+$srcdir/test/rule-syntax-check.py `find $srcdir/rules -name '*.rules'`
diff --git a/test/sys.tar.xz b/test/sys.tar.xz
new file mode 100644
index 0000000..49ee802
--- /dev/null
+++ b/test/sys.tar.xz
Binary files differ
diff --git a/test/udev-test.pl b/test/udev-test.pl
new file mode 100755
index 0000000..0b379b0
--- /dev/null
+++ b/test/udev-test.pl
@@ -0,0 +1,1560 @@
+#!/usr/bin/perl
+
+# udev test
+#
+# Provides automated testing of the udev binary.
+# The whole test is self contained in this file, except the matching sysfs tree.
+# Simply extend the @tests array, to add a new test variant.
+#
+# Every test is driven by its own temporary config file.
+# This program prepares the environment, creates the config and calls udev.
+#
+# udev parses the rules, looks at the provided sysfs and
+# first creates and then removes the device node.
+# After creation and removal the result is checked against the
+# expected value and the result is printed.
+#
+# Copyright (C) 2004-2011 Kay Sievers <kay.sievers@vrfy.org>
+# Copyright (C) 2004 Leann Ogasawara <ogasawara@osdl.org>
+
+use warnings;
+use strict;
+
+my $PWD                 = $ENV{PWD};
+my $sysfs               = "test/sys";
+my $udev_bin            = "./test-udev";
+my $valgrind            = 0;
+my $udev_bin_valgrind   = "valgrind --tool=memcheck --leak-check=yes --quiet $udev_bin";
+my $udev_root           = "udev-root";
+my $udev_conf           = "udev-test.conf";
+my $udev_rules          = "udev-test.rules";
+
+my @tests = (
+        {
+                desc            => "no rules",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "sda" ,
+                exp_rem_error   => "yes",
+                rules           => <<EOF
+#
+EOF
+        },
+        {
+                desc            => "label test of scsi disc",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "boot_disk" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "label test of scsi disc",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "boot_disk" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "label test of scsi disc",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "boot_disk" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "label test of scsi partition",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "boot_disk1" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+EOF
+        },
+        {
+                desc            => "label test of pattern match",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "boot_disk1" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATAS", SYMLINK+="boot_disk%n-3"
+EOF
+        },
+        {
+                desc            => "label test of multiple sysfs files",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "boot_disk1" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n"
+EOF
+        },
+        {
+                desc            => "label test of max sysfs files (skip invalid rule)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "boot_disk1" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+EOF
+        },
+        {
+                desc            => "catch device by *",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem/0" ,
+                rules           => <<EOF
+KERNEL=="ttyACM*", SYMLINK+="modem/%n"
+EOF
+        },
+        {
+                desc            => "catch device by * - take 2",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem/0" ,
+                rules           => <<EOF
+KERNEL=="*ACM1", SYMLINK+="bad"
+KERNEL=="*ACM0", SYMLINK+="modem/%n"
+EOF
+        },
+        {
+                desc            => "catch device by ?",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem/0" ,
+                rules           => <<EOF
+KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1"
+KERNEL=="ttyACM??", SYMLINK+="modem/%n-2"
+KERNEL=="ttyACM?", SYMLINK+="modem/%n"
+EOF
+        },
+        {
+                desc            => "catch device by character class",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem/0" ,
+                rules           => <<EOF
+KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1"
+KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2"
+KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n"
+EOF
+        },
+        {
+                desc            => "replace kernel name",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "Handle comment lines in config file (and replace kernel name)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+# this is a comment
+KERNEL=="ttyACM0", SYMLINK+="modem"
+
+EOF
+        },
+        {
+                desc            => "Handle comment lines in config file with whitespace (and replace kernel name)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+ # this is a comment with whitespace before the comment
+KERNEL=="ttyACM0", SYMLINK+="modem"
+
+EOF
+        },
+        {
+                desc            => "Handle whitespace only lines (and replace kernel name)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "whitespace" ,
+                rules           => <<EOF
+
+
+
+ # this is a comment with whitespace before the comment
+KERNEL=="ttyACM0", SYMLINK+="whitespace"
+
+
+
+EOF
+        },
+        {
+                desc            => "Handle empty lines in config file (and replace kernel name)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+
+KERNEL=="ttyACM0", SYMLINK+="modem"
+
+EOF
+        },
+        {
+                desc            => "Handle backslashed multi lines in config file (and replace kernel name)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+KERNEL=="ttyACM0", \\
+SYMLINK+="modem"
+
+EOF
+        },
+        {
+                desc            => "preserve backslashes, if they are not for a newline",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "aaa",
+                rules           => <<EOF
+KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \\101", RESULT=="A", SYMLINK+="aaa"
+EOF
+        },
+        {
+                desc            => "Handle stupid backslashed multi lines in config file (and replace kernel name)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+
+#
+\\
+
+\\
+
+#\\
+
+KERNEL=="ttyACM0", \\
+        SYMLINK+="modem"
+
+EOF
+        },
+        {
+                desc            => "subdirectory handling",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "sub/direct/ory/modem" ,
+                rules           => <<EOF
+KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem"
+EOF
+        },
+        {
+                desc            => "parent device name match of scsi partition",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "first_disk5" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n"
+EOF
+        },
+        {
+                desc            => "test substitution chars",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b"
+EOF
+        },
+        {
+                desc            => "import of shell-value file",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "subdir/err/node" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", IMPORT{file}="udev-test.conf", SYMLINK+="subdir/%E{udev_log}/node"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "import of shell-value returned from program",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node12345678",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e \' TEST_KEY=12345678\\n  TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "sustitution of sysfs value (%s{file})",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "disk-ATA-sda" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "program result substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "special-device-5" ,
+                not_exp_name    => "not" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not"
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n"
+EOF
+        },
+        {
+                desc            => "program result substitution (newline removal)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "newline_removed" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed"
+EOF
+        },
+        {
+                desc            => "program result substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "test-0:0:0:0" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c"
+EOF
+        },
+        {
+                desc            => "program with lots of arguments",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "foo9" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}"
+EOF
+        },
+        {
+                desc            => "program with subshell",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "bar9" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed  s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}"
+EOF
+        },
+        {
+                desc            => "program arguments combined with apostrophes",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "foo7" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4'   'foo5   foo6   foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}"
+EOF
+        },
+        {
+                desc            => "characters before the %c{N} substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "my-foo9" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}"
+EOF
+        },
+        {
+                desc            => "substitute the second to last argument",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "my-foo8" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}"
+EOF
+        },
+        {
+                desc            => "test substitution by variable name",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:\$major-minor:\$minor-kernelnumber:\$number-id:\$id"
+EOF
+        },
+        {
+                desc            => "test substitution by variable name 2",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:\$major-minor:%m-kernelnumber:\$number-id:\$id"
+EOF
+        },
+        {
+                desc            => "test substitution by variable name 3",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "850:0:0:05" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n"
+EOF
+        },
+        {
+                desc            => "test substitution by variable name 4",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "855" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major\$minor\$number"
+EOF
+        },
+        {
+                desc            => "test substitution by variable name 5",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "8550:0:0:0" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major%m%n\$id"
+EOF
+        },
+        {
+                desc            => "non matching SUBSYSTEMS for device with no parent",
+                devpath         => "/devices/virtual/tty/console",
+                exp_name        => "TTY",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo"
+KERNEL=="console", SYMLINK+="TTY"
+EOF
+        },
+        {
+                desc            => "non matching SUBSYSTEMS",
+                devpath         => "/devices/virtual/tty/console",
+                exp_name        => "TTY" ,
+                rules                => <<EOF
+SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo"
+KERNEL=="console", SYMLINK+="TTY"
+EOF
+        },
+        {
+                desc            => "ATTRS match",
+                devpath         => "/devices/virtual/tty/console",
+                exp_name        => "foo" ,
+                rules           => <<EOF
+KERNEL=="console", SYMLINK+="TTY"
+ATTRS{dev}=="5:1", SYMLINK+="foo"
+EOF
+        },
+        {
+                desc            => "ATTR (empty file)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "empty" ,
+                rules           => <<EOF
+KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something"
+KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty"
+KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty"
+KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something"
+EOF
+        },
+        {
+                desc            => "ATTR (non-existent file)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "non-existent" ,
+                rules           => <<EOF
+KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something"
+KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty"
+KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty"
+KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something"
+KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent"
+KERNEL=="sda", SYMLINK+="wrong"
+EOF
+        },
+        {
+                desc            => "program and bus type match",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "scsi-0:0:0:0" ,
+                rules           => <<EOF
+SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c"
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c"
+SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c"
+EOF
+        },
+        {
+                desc            => "sysfs parent hierarchy",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem" ,
+                rules           => <<EOF
+ATTRS{idProduct}=="007b", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "name test with ! in the name",
+                devpath         => "/devices/virtual/block/fake!blockdev0",
+                exp_name        => "is/a/fake/blockdev0" ,
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k"
+SUBSYSTEM=="block", SYMLINK+="is/a/%k"
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "name test with ! in the name, but no matching rule",
+                devpath         => "/devices/virtual/block/fake!blockdev0",
+                exp_name        => "fake/blockdev0" ,
+                exp_rem_error   => "yes",
+                rules           => <<EOF
+KERNEL=="ttyACM0", SYMLINK+="modem"
+EOF
+        },
+        {
+                desc            => "KERNELS rule",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "scsi-0:0:0:0",
+                rules           => <<EOF
+SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi"
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match"
+SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id"
+SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match"
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0"
+EOF
+        },
+        {
+                desc            => "KERNELS wildcard all",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "scsi-0:0:0:0",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match"
+SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match"
+SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match"
+SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before"
+SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0"
+EOF
+        },
+        {
+                desc            => "KERNELS wildcard partial",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "scsi-0:0:0:0",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0"
+EOF
+        },
+        {
+                desc            => "KERNELS wildcard partial 2",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "scsi-0:0:0:0",
+                rules                => <<EOF
+SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0"
+EOF
+        },
+        {
+                desc            => "substitute attr with link target value (first match)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "driver-is-sd",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", SYMLINK+="driver-is-\$attr{driver}"
+EOF
+        },
+        {
+                desc            => "substitute attr with link target value (currently selected device)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "driver-is-ahci",
+                rules           => <<EOF
+SUBSYSTEMS=="pci", SYMLINK+="driver-is-\$attr{driver}"
+EOF
+        },
+        {
+                desc            => "ignore ATTRS attribute whitespace",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "ignored",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE", SYMLINK+="ignored"
+EOF
+        },
+        {
+                desc            => "do not ignore ATTRS attribute whitespace",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "matched-with-space",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE ", SYMLINK+="wrong-to-ignore"
+SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE   ", SYMLINK+="matched-with-space"
+EOF
+        },
+        {
+                desc            => "permissions USER=bad GROUP=name",
+                devpath         => "/devices/virtual/tty/tty33",
+                exp_name        => "tty33",
+                exp_perms       => "0:0:0600",
+                rules           => <<EOF
+KERNEL=="tty33", SYMLINK+="tty33", OWNER="bad", GROUP="name"
+EOF
+        },
+        {
+                desc            => "permissions OWNER=5000",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => "5000::0600",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="5000"
+EOF
+        },
+        {
+                desc            => "permissions GROUP=100",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => ":100:0660",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="100"
+EOF
+        },
+        {
+                desc            => "textual user id",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => "nobody::0600",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="nobody"
+EOF
+        },
+        {
+                desc            => "textual group id",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => ":daemon:0660",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon"
+EOF
+        },
+        {
+                desc            => "textual user/group id",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => "root:mail:0660",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="mail"
+EOF
+        },
+        {
+                desc            => "permissions MODE=0777",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => "::0777",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777"
+EOF
+        },
+        {
+                desc            => "permissions OWNER=5000 GROUP=100 MODE=0777",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_perms       => "5000:100:0777",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="5000", GROUP="100", MODE="0777"
+EOF
+        },
+        {
+                desc            => "permissions OWNER to 5000",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => "5000::",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="5000"
+EOF
+        },
+        {
+                desc            => "permissions GROUP to 100",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => ":100:0660",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="100"
+EOF
+        },
+        {
+                desc            => "permissions MODE to 0060",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => "::0060",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060"
+EOF
+        },
+        {
+                desc            => "permissions OWNER, GROUP, MODE",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => "5000:100:0777",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="5000", GROUP="100", MODE="0777"
+EOF
+        },
+        {
+                desc            => "permissions only rule",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => "5000:100:0777",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", OWNER="5000", GROUP="100", MODE="0777"
+KERNEL=="ttyUSX[0-9]*", OWNER="5001", GROUP="101", MODE="0444"
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n"
+EOF
+        },
+        {
+                desc            => "multiple permissions only rule",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => "3000:4000:0777",
+                rules           => <<EOF
+SUBSYSTEM=="tty", OWNER="3000"
+SUBSYSTEM=="tty", GROUP="4000"
+SUBSYSTEM=="tty", MODE="0777"
+KERNEL=="ttyUSX[0-9]*", OWNER="5001", GROUP="101", MODE="0444"
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n"
+EOF
+        },
+        {
+                desc            => "permissions only rule with override at SYMLINK+ rule",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "ttyACM0",
+                exp_perms       => "3000:8000:0777",
+                rules           => <<EOF
+SUBSYSTEM=="tty", OWNER="3000"
+SUBSYSTEM=="tty", GROUP="4000"
+SUBSYSTEM=="tty", MODE="0777"
+KERNEL=="ttyUSX[0-9]*", OWNER="5001", GROUP="101", MODE="0444"
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="8000"
+EOF
+        },
+        {
+                desc            => "major/minor number test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                exp_majorminor  => "8:0",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node"
+EOF
+        },
+        {
+                desc            => "big major number test",
+                devpath         => "/devices/virtual/misc/misc-fake1",
+                exp_name        => "node",
+                exp_majorminor  => "4095:1",
+                rules                => <<EOF
+KERNEL=="misc-fake1", SYMLINK+="node"
+EOF
+        },
+        {
+                desc            => "big major and big minor number test",
+                devpath         => "/devices/virtual/misc/misc-fake89999",
+                exp_name        => "node",
+                exp_majorminor  => "4095:89999",
+                rules           => <<EOF
+KERNEL=="misc-fake89999", SYMLINK+="node"
+EOF
+        },
+        {
+                desc            => "multiple symlinks with format char",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "symlink2-ttyACM0",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b"
+EOF
+        },
+        {
+                desc            => "multiple symlinks with a lot of s p a c e s",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "one",
+                not_exp_name        => " ",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK="  one     two        "
+EOF
+        },
+        {
+                desc            => "symlink creation (same directory)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "modem0",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n"
+EOF
+        },
+        {
+                desc            => "multiple symlinks",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "second-0" ,
+                rules           => <<EOF
+KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n"
+EOF
+        },
+        {
+                desc            => "symlink name '.'",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => ".",
+                exp_add_error        => "yes",
+                exp_rem_error        => "yes",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="."
+EOF
+        },
+        {
+                desc            => "symlink node to itself",
+                devpath         => "/devices/virtual/tty/tty0",
+                exp_name        => "link",
+                exp_add_error        => "yes",
+                exp_rem_error        => "yes",
+                option                => "clean",
+                rules           => <<EOF
+KERNEL=="tty0", SYMLINK+="tty0"
+EOF
+        },
+        {
+                desc            => "symlink %n substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "symlink0",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n"
+EOF
+        },
+        {
+                desc            => "symlink %k substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "symlink-ttyACM0",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k"
+EOF
+        },
+        {
+                desc            => "symlink %M:%m substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "major-166:0",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m"
+EOF
+        },
+        {
+                desc            => "symlink %b substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "symlink-0:0:0:0",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b"
+EOF
+        },
+        {
+                desc            => "symlink %c substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "test",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c"
+EOF
+        },
+        {
+                desc            => "symlink %c{N} substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "test",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}"
+EOF
+        },
+        {
+                desc            => "symlink %c{N+} substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "this",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}"
+EOF
+        },
+        {
+                desc            => "symlink only rule with %c{N+}",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "test",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}"
+EOF
+        },
+        {
+                desc            => "symlink %s{filename} substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "166:0",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}"
+EOF
+        },
+        {
+                desc            => "program result substitution (numbered part of)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "link1",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}"
+EOF
+        },
+        {
+                desc            => "program result substitution (numbered part of+)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+                exp_name        => "link4",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}"
+EOF
+        },
+        {
+                desc            => "SUBSYSTEM match test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc"
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block"
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc"
+EOF
+        },
+        {
+                desc            => "DRIVERS match test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong"
+SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd"
+EOF
+        },
+        {
+                desc            => "devnode substitution test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node"
+EOF
+        },
+        {
+                desc            => "parent node name substitution test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "sda-part-1",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-1"
+EOF
+        },
+        {
+                desc            => "udev_root substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "start-udev-root-end",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end"
+EOF
+        },
+        {
+                desc            => "last_rule option",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "last",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last"
+EOF
+        },
+        {
+                desc            => "negation KERNEL!=",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "match",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match"
+EOF
+        },
+        {
+                desc            => "negation SUBSYSTEM!=",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "not-anything",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything"
+EOF
+        },
+        {
+                desc            => "negation PROGRAM!= exit code",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "nonzero-program",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program"
+EOF
+        },
+        {
+                desc            => "test for whitespace between the operator",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "true",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+KERNEL   ==   "sda1"     ,    SYMLINK+   =    "true"
+EOF
+        },
+        {
+                desc            => "ENV{} test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "true",
+                rules           => <<EOF
+ENV{ENV_KEY_TEST}="test"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad"
+EOF
+        },
+        {
+                desc            => "ENV{} test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "true",
+                rules           => <<EOF
+ENV{ENV_KEY_TEST}="test"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad"
+EOF
+        },
+        {
+                desc            => "ENV{} test (assign)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "true",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true"
+EOF
+        },
+        {
+                desc            => "ENV{} test (assign 2 times)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "true",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-\$env{ASSIGN}"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no"
+SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true"
+EOF
+        },
+        {
+                desc            => "ENV{} test (assign2)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "part",
+                rules           => <<EOF
+SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false"
+SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true"
+ENV{MAINDEVICE}=="true", SYMLINK+="disk"
+SUBSYSTEM=="block", SYMLINK+="before"
+ENV{PARTITION}=="true", SYMLINK+="part"
+EOF
+        },
+        {
+                desc            => "untrusted string sanitize",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "sane",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane"
+EOF
+        },
+        {
+                desc            => "untrusted string sanitize (don't replace utf8)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "uber",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xc3\\xbcber" RESULT=="\xc3\xbcber", SYMLINK+="uber"
+EOF
+        },
+        {
+                desc            => "untrusted string sanitize (replace invalid utf8)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "replaced",
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xef\\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced"
+EOF
+        },
+        {
+                desc            => "read sysfs value from parent device",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "serial-354172020305000",
+                rules           => <<EOF
+KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}"
+EOF
+        },
+        {
+                desc            => "match against empty key string",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "ok",
+                rules           => <<EOF
+KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok"
+KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok"
+KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok"
+KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok"
+EOF
+        },
+        {
+                desc            => "check ACTION value",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "ok",
+                rules           => <<EOF
+ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok"
+ACTION=="add", KERNEL=="sda", SYMLINK+="ok"
+EOF
+        },
+        {
+                desc            => "final assignment",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "ok",
+                exp_perms       => "root:tty:0640",
+                rules           => <<EOF
+KERNEL=="sda", GROUP:="tty"
+KERNEL=="sda", GROUP="not-ok", MODE="0640", SYMLINK+="ok"
+EOF
+        },
+        {
+                desc            => "final assignment 2",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "ok",
+                exp_perms       => "root:tty:0640",
+                rules           => <<EOF
+KERNEL=="sda", GROUP:="tty"
+SUBSYSTEM=="block", MODE:="640"
+KERNEL=="sda", GROUP="not-ok", MODE="0666", SYMLINK+="ok"
+EOF
+        },
+        {
+                desc            => "env substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "node-add-me",
+                rules           => <<EOF
+KERNEL=="sda", MODE="0666", SYMLINK+="node-\$env{ACTION}-me"
+EOF
+        },
+        {
+                desc            => "reset list to current value",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "three",
+                not_exp_name    => "two",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="one"
+KERNEL=="ttyACM[0-9]*", SYMLINK+="two"
+KERNEL=="ttyACM[0-9]*", SYMLINK="three"
+EOF
+        },
+        {
+                desc            => "test empty SYMLINK+ (empty override)",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "right",
+                not_exp_name    => "wrong",
+                rules           => <<EOF
+KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong"
+KERNEL=="ttyACM[0-9]*", SYMLINK=""
+KERNEL=="ttyACM[0-9]*", SYMLINK+="right"
+EOF
+        },
+        {
+                desc            => "test multi matches",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "right",
+                rules           => <<EOF
+KERNEL=="ttyACM*", SYMLINK+="before"
+KERNEL=="ttyACM*|nothing", SYMLINK+="right"
+EOF
+        },
+        {
+                desc            => "test multi matches 2",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "right",
+                rules           => <<EOF
+KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch"
+KERNEL=="ttyACM*", SYMLINK+="before"
+KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right"
+EOF
+        },
+        {
+                desc            => "test multi matches 3",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "right",
+                rules           => <<EOF
+KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
+KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
+KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2"
+KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right"
+EOF
+        },
+        {
+                desc            => "test multi matches 4",
+                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                exp_name        => "right",
+                rules           => <<EOF
+KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
+KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
+KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2"
+KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right"
+KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3"
+EOF
+        },
+        {
+                desc            => "IMPORT parent test sequence 1/2 (keep)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "parent",
+                option          => "keep",
+                rules           => <<EOF
+KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'"
+KERNEL=="sda", SYMLINK+="parent"
+EOF
+        },
+        {
+                desc            => "IMPORT parent test sequence 2/2 (keep)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "parentenv-parent_right",
+                option          => "clean",
+                rules           => <<EOF
+KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-\$env{PARENT_KEY}\$env{WRONG_PARENT_KEY}"
+EOF
+        },
+        {
+                desc            => "GOTO test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "right",
+                rules           => <<EOF
+KERNEL=="sda1", GOTO="TEST"
+KERNEL=="sda1", SYMLINK+="wrong"
+KERNEL=="sda1", GOTO="BAD"
+KERNEL=="sda1", SYMLINK+="", LABEL="NO"
+KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end"
+KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD"
+LABEL="end"
+EOF
+        },
+        {
+                desc            => "GOTO label does not exist",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "right",
+                rules           => <<EOF
+KERNEL=="sda1", GOTO="does-not-exist"
+KERNEL=="sda1", SYMLINK+="right",
+LABEL="exists"
+EOF
+        },
+        {
+                desc            => "SYMLINK+ compare test",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "right",
+                not_exp_name    => "wrong",
+                rules           => <<EOF
+KERNEL=="sda1", SYMLINK+="link"
+KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right"
+KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong"
+EOF
+        },
+        {
+                desc            => "invalid key operation",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "yes",
+                rules           => <<EOF
+KERNEL="sda1", SYMLINK+="no"
+KERNEL=="sda1", SYMLINK+="yes"
+EOF
+        },
+        {
+                desc            => "operator chars in attribute",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "yes",
+                rules           => <<EOF
+KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes"
+EOF
+        },
+        {
+                desc            => "overlong comment line",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                exp_name        => "yes",
+                rules           => <<EOF
+# 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+   # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+KERNEL=="sda1", SYMLINK+=="no"
+KERNEL=="sda1", SYMLINK+="yes"
+EOF
+        },
+        {
+                desc            => "magic subsys/kernel lookup",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "00:16:41:e2:8d:ff",
+                rules           => <<EOF
+KERNEL=="sda", SYMLINK+="\$attr{[net/eth0]address}"
+EOF
+        },
+        {
+                desc            => "TEST absolute path",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "there",
+                rules           => <<EOF
+TEST=="/etc/hosts", SYMLINK+="there"
+TEST!="/etc/hosts", SYMLINK+="notthere"
+EOF
+        },
+        {
+                desc            => "TEST subsys/kernel lookup",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "yes",
+                rules           => <<EOF
+KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes"
+EOF
+        },
+        {
+                desc            => "TEST relative path",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "relative",
+                rules           => <<EOF
+KERNEL=="sda", TEST=="size", SYMLINK+="relative"
+EOF
+        },
+        {
+                desc            => "TEST wildcard substitution (find queue/nr_requests)",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "found-subdir",
+                rules           => <<EOF
+KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir"
+EOF
+        },
+        {
+                desc            => "TEST MODE=0000",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "sda",
+                exp_perms       => "0:0:0000",
+                exp_rem_error   => "yes",
+                rules           => <<EOF
+KERNEL=="sda", MODE="0000"
+EOF
+        },
+        {
+                desc            => "TEST PROGRAM feeds OWNER, GROUP, MODE",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "sda",
+                exp_perms       => "5000:100:0400",
+                exp_rem_error   => "yes",
+                rules           => <<EOF
+KERNEL=="sda", MODE="666"
+KERNEL=="sda", PROGRAM=="/bin/echo 5000 100 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
+EOF
+        },
+        {
+                desc            => "TEST PROGRAM feeds MODE with overflow",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "sda",
+                exp_perms       => "0:0:0440",
+                exp_rem_error   => "yes",
+                rules           => <<EOF
+KERNEL=="sda", MODE="440"
+KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
+EOF
+        },
+        {
+                desc            => "magic [subsys/sysname] attribute substitution",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "sda-8741C4G-end",
+                exp_perms       => "0:0:0600",
+                rules           => <<EOF
+KERNEL=="sda", PROGRAM="/bin/true create-envp"
+KERNEL=="sda", ENV{TESTENV}="change-envp"
+KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end"
+EOF
+        },
+        {
+                desc            => "builtin path_id",
+                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                exp_name        => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0",
+                rules           => <<EOF
+KERNEL=="sda", IMPORT{builtin}="path_id"
+KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/\$env{ID_PATH}"
+EOF
+        },
+);
+
+# set env
+$ENV{UDEV_CONFIG_FILE} = $udev_conf;
+
+sub udev {
+        my ($action, $devpath, $rules) = @_;
+
+        # create temporary rules
+        open CONF, ">$udev_rules" || die "unable to create rules file: $udev_rules";
+        print CONF $$rules;
+        close CONF;
+
+        if ($valgrind > 0) {
+                system("$udev_bin_valgrind $action $devpath");
+        } else {
+                system("$udev_bin $action $devpath");
+        }
+}
+
+my $error = 0;
+
+sub permissions_test {
+        my($rules, $uid, $gid, $mode) = @_;
+
+        my $wrong = 0;
+        my $userid;
+        my $groupid;
+
+        $rules->{exp_perms} =~ m/^(.*):(.*):(.*)$/;
+        if ($1 ne "") {
+                if (defined(getpwnam($1))) {
+                        $userid = int(getpwnam($1));
+                } else {
+                        $userid = $1;
+                }
+                if ($uid != $userid) { $wrong = 1; }
+        }
+        if ($2 ne "") {
+                if (defined(getgrnam($2))) {
+                        $groupid = int(getgrnam($2));
+                } else {
+                        $groupid = $2;
+                }
+                if ($gid != $groupid) { $wrong = 1; }
+        }
+        if ($3 ne "") {
+                if (($mode & 07777) != oct($3)) { $wrong = 1; };
+        }
+        if ($wrong == 0) {
+                print "permissions: ok\n";
+        } else {
+                printf "  expected permissions are: %s:%s:%#o\n", $1, $2, oct($3);
+                printf "  created permissions are : %i:%i:%#o\n", $uid, $gid, $mode & 07777;
+                print "permissions: error\n";
+                $error++;
+                sleep(1);
+        }
+}
+
+sub major_minor_test {
+        my($rules, $rdev) = @_;
+
+        my $major = ($rdev >> 8) & 0xfff;
+        my $minor = ($rdev & 0xff) | (($rdev >> 12) & 0xfff00);
+        my $wrong = 0;
+
+        $rules->{exp_majorminor} =~ m/^(.*):(.*)$/;
+        if ($1 ne "") {
+                if ($major != $1) { $wrong = 1; };
+        }
+        if ($2 ne "") {
+                if ($minor != $2) { $wrong = 1; };
+        }
+        if ($wrong == 0) {
+                print "major:minor: ok\n";
+        } else {
+                printf "  expected major:minor is: %i:%i\n", $1, $2;
+                printf "  created major:minor is : %i:%i\n", $major, $minor;
+                print "major:minor: error\n";
+                $error++;
+                sleep(1);
+        }
+}
+
+sub make_udev_root {
+        system("rm -rf $udev_root");
+        mkdir($udev_root) || die "unable to create udev_root: $udev_root\n";
+        # setting group and mode of udev_root ensures the tests work
+        # even if the parent directory has setgid bit enabled.
+        chown (0, 0, $udev_root) || die "unable to chown $udev_root\n";
+        chmod (0755, $udev_root) || die "unable to chmod $udev_root\n";
+}
+
+sub run_test {
+        my ($rules, $number) = @_;
+
+        print "TEST $number: $rules->{desc}\n";
+        print "device \'$rules->{devpath}\' expecting node/link \'$rules->{exp_name}\'\n";
+
+        udev("add", $rules->{devpath}, \$rules->{rules});
+        if (defined($rules->{not_exp_name})) {
+                if ((-e "$PWD/$udev_root/$rules->{not_exp_name}") ||
+                    (-l "$PWD/$udev_root/$rules->{not_exp_name}")) {
+                        print "nonexistent: error \'$rules->{not_exp_name}\' not expected to be there\n";
+                        $error++;
+                        sleep(1);
+                }
+        }
+
+        if ((-e "$PWD/$udev_root/$rules->{exp_name}") ||
+            (-l "$PWD/$udev_root/$rules->{exp_name}")) {
+
+                my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
+                    $atime, $mtime, $ctime, $blksize, $blocks) = stat("$PWD/$udev_root/$rules->{exp_name}");
+
+                if (defined($rules->{exp_perms})) {
+                        permissions_test($rules, $uid, $gid, $mode);
+                }
+                if (defined($rules->{exp_majorminor})) {
+                        major_minor_test($rules, $rdev);
+                }
+                print "add:         ok\n";
+        } else {
+                print "add:         error";
+                if ($rules->{exp_add_error}) {
+                        print " as expected\n";
+                } else {
+                        print "\n";
+                        system("tree $udev_root");
+                        print "\n";
+                        $error++;
+                        sleep(1);
+                }
+        }
+
+        if (defined($rules->{option}) && $rules->{option} eq "keep") {
+                print "\n\n";
+                return;
+        }
+
+        udev("remove", $rules->{devpath}, \$rules->{rules});
+        if ((-e "$PWD/$udev_root/$rules->{exp_name}") ||
+            (-l "$PWD/$udev_root/$rules->{exp_name}")) {
+                print "remove:      error";
+                if ($rules->{exp_rem_error}) {
+                        print " as expected\n";
+                } else {
+                        print "\n";
+                        system("tree $udev_root");
+                        print "\n";
+                        $error++;
+                        sleep(1);
+                }
+        } else {
+                print "remove:      ok\n";
+        }
+
+        print "\n";
+
+        if (defined($rules->{option}) && $rules->{option} eq "clean") {
+                make_udev_root();
+        }
+
+}
+
+# only run if we have root permissions
+# due to mknod restrictions
+if (!($<==0)) {
+        print "Must have root permissions to run properly.\n";
+        exit;
+}
+
+# prepare
+make_udev_root();
+
+# create config file
+open CONF, ">$udev_conf" || die "unable to create config file: $udev_conf";
+print CONF "udev_root=\"$udev_root\"\n";
+print CONF "udev_run=\"$udev_root/.udev\"\n";
+print CONF "udev_sys=\"$sysfs\"\n";
+print CONF "udev_rules=\"$PWD\"\n";
+print CONF "udev_log=\"err\"\n";
+close CONF;
+
+my $test_num = 1;
+my @list;
+
+foreach my $arg (@ARGV) {
+        if ($arg =~ m/--valgrind/) {
+                $valgrind = 1;
+                printf("using valgrind\n");
+        } else {
+                push(@list, $arg);
+        }
+}
+
+if ($list[0]) {
+        foreach my $arg (@list) {
+                if (defined($tests[$arg-1]->{desc})) {
+                        print "udev-test will run test number $arg:\n\n";
+                        run_test($tests[$arg-1], $arg);
+                } else {
+                        print "test does not exist.\n";
+                }
+        }
+} else {
+        # test all
+        print "\nudev-test will run ".($#tests + 1)." tests:\n\n";
+
+        foreach my $rules (@tests) {
+                run_test($rules, $test_num);
+                $test_num++;
+        }
+}
+
+print "$error errors occured\n\n";
+
+# cleanup
+system("rm -rf $udev_root");
+unlink($udev_rules);
+unlink($udev_conf);
+
+if ($error > 0) {
+    exit(1);
+}
+exit(0);
diff --git a/usr/lib/libgudev-1.0.so.0 b/usr/lib/libgudev-1.0.so.0
deleted file mode 120000
index 83148bc..0000000
--- a/usr/lib/libgudev-1.0.so.0
+++ /dev/null
@@ -1 +0,0 @@
-libgudev-1.0.so.0.1.1
\ No newline at end of file
diff --git a/usr/lib/libgudev-1.0.so.0.1.1 b/usr/lib/libgudev-1.0.so.0.1.1
deleted file mode 100755
index a8392e4..0000000
--- a/usr/lib/libgudev-1.0.so.0.1.1
+++ /dev/null
Binary files differ
diff --git a/usr/lib/libudev.so.0 b/usr/lib/libudev.so.0
deleted file mode 120000
index a57a1aa..0000000
--- a/usr/lib/libudev.so.0
+++ /dev/null
@@ -1 +0,0 @@
-libudev.so.0.13.0
\ No newline at end of file
diff --git a/usr/lib/libudev.so.0.13.0 b/usr/lib/libudev.so.0.13.0
deleted file mode 100755
index 5646957..0000000
--- a/usr/lib/libudev.so.0.13.0
+++ /dev/null
Binary files differ
diff --git a/usr/sbin/udevadm b/usr/sbin/udevadm
deleted file mode 100755
index 0d46294..0000000
--- a/usr/sbin/udevadm
+++ /dev/null
Binary files differ