summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenedikt Boehm <hollow@gentoo.org>2005-09-03 16:10:27 +0000
committerBenedikt Boehm <hollow@gentoo.org>2005-09-03 16:10:27 +0000
commit15d9155808de928fa26bfeaaecfee7210afde0d3 (patch)
treef113365bda736d949d39dd94bd99346056030e30 /src
downloadbaselayout-vserver-15d9155808de928fa26bfeaaecfee7210afde0d3.tar.gz
baselayout-vserver-15d9155808de928fa26bfeaaecfee7210afde0d3.tar.bz2
baselayout-vserver-15d9155808de928fa26bfeaaecfee7210afde0d3.zip
import initial baselayout sources (1.12.0_pre8)
svn path=/baselayout-vserver/trunk/; revision=3
Diffstat (limited to 'src')
-rw-r--r--src/.cvsignore5
-rw-r--r--src/Makefile49
-rw-r--r--src/awk/cachedepends.awk210
-rw-r--r--src/awk/functions.awk156
-rw-r--r--src/awk/gendepends.awk562
-rw-r--r--src/awk/genenviron.awk179
-rw-r--r--src/consoletype.c38
-rw-r--r--src/core/.cvsignore3
-rw-r--r--src/core/ChangeLog163
-rw-r--r--src/core/Makefile81
-rw-r--r--src/core/README15
-rw-r--r--src/core/debug.h85
-rw-r--r--src/core/depend.c532
-rw-r--r--src/core/depend.h75
-rw-r--r--src/core/depscan.c297
-rw-r--r--src/core/list.h446
-rw-r--r--src/core/misc.c649
-rw-r--r--src/core/misc.h288
-rw-r--r--src/core/parse.c1033
-rw-r--r--src/core/parse.h110
-rw-r--r--src/core/simple-regex.c854
-rw-r--r--src/core/simple-regex.h86
-rw-r--r--src/core/test-regex.c80
-rw-r--r--src/env_whitelist26
-rw-r--r--src/filefuncs/Makefile17
-rw-r--r--src/filefuncs/filefuncs.c486
-rw-r--r--src/headers.h26
-rw-r--r--src/runscript.c250
-rw-r--r--src/start-stop-daemon.c1375
29 files changed, 8176 insertions, 0 deletions
diff --git a/src/.cvsignore b/src/.cvsignore
new file mode 100644
index 0000000..a26e40b
--- /dev/null
+++ b/src/.cvsignore
@@ -0,0 +1,5 @@
+.cvsignore
+*.o
+consoletype
+runscript
+start-stop-daemon
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..252dea0
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,49 @@
+# Copyright 1999-2005 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header$
+
+CC = gcc
+LD = gcc
+
+CFLAGS = -Wall -O2
+DESTDIR =
+LIBDIR = lib
+
+BIN_TARGETS =
+SBIN_TARGETS = consoletype runscript start-stop-daemon
+SYS_WHITELIST = env_whitelist
+
+TARGET = $(BIN_TARGETS) $(SBIN_TARGETS)
+
+OS = Linux
+ifeq ($(OS),Linux)
+LDFLAGS_RS = -ldl
+endif
+ifeq ($(OS),BSD)
+LDFLAGS_SSD = -lkvm
+endif
+
+override CFLAGS += -DLIBDIR=\"$(LIBDIR)\"
+
+all: $(TARGET)
+
+rs-misc.o: core/misc.c
+ $(CC) $(CFLAGS) -c -o $@ $^
+
+runscript: runscript.o rs-misc.o
+ $(LD) $(LDFLAGS) -o $@ $^ $(LDFLAGS_RS)
+
+start-stop-daemon: start-stop-daemon.c
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDFLAGS_SSD)
+
+install: $(TARGET)
+ install -m 0755 -d $(DESTDIR)/bin
+ install -m 0755 -d $(DESTDIR)/sbin
+# install -m 0755 $(BIN_TARGETS) $(DESTDIR)/bin
+ install -m 0755 $(SBIN_TARGETS) $(DESTDIR)/sbin
+ install -m 0755 -d $(DESTDIR)/$(LIBDIR)/rcscripts/conf.d
+ install -m 0644 $(SYS_WHITELIST) $(DESTDIR)/$(LIBDIR)/rcscripts/conf.d
+
+clean:
+ rm -f $(TARGET)
+ rm -f *.o *~
diff --git a/src/awk/cachedepends.awk b/src/awk/cachedepends.awk
new file mode 100644
index 0000000..017e231
--- /dev/null
+++ b/src/awk/cachedepends.awk
@@ -0,0 +1,210 @@
+# Copyright 1999-2004 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header$
+
+function print_start() {
+ print "source /sbin/functions.sh" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "need() {" >> TMPCACHE
+ print " echo \"NEED $*\"; return 0" >> TMPCACHE
+ print "}" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "use() {" >> TMPCACHE
+ print " echo \"USE $*\"; return 0" >> TMPCACHE
+ print "}" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "before() {" >> TMPCACHE
+ print " echo \"BEFORE $*\"; return 0" >> TMPCACHE
+ print "}" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "after() {" >> TMPCACHE
+ print " echo \"AFTER $*\"; return 0" >> TMPCACHE
+ print "}" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "provide() {" >> TMPCACHE
+ print " echo \"PROVIDE $*\"; return 0" >> TMPCACHE
+ print "}" >> TMPCACHE
+ print "" >> TMPCACHE
+}
+
+function print_header1(mtime) {
+ print "#*** " MYFILENAME " ***" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "myservice=\"" MYFILENAME "\"" >> TMPCACHE
+ print "myservice=\"${myservice##*/}\"" >> TMPCACHE
+ print "echo \"RCSCRIPT ${myservice}\"" >> TMPCACHE
+ print "" >> TMPCACHE
+ print "echo \"MTIME " mtime "\"" >> TMPCACHE
+ print "" >> TMPCACHE
+}
+
+function print_header2(mtime) {
+ print "(" >> TMPCACHE
+ print " # Get settings for rc-script ..." >> TMPCACHE
+ print "" >> TMPCACHE
+ print " [ -e \"/etc/conf.d/${myservice}\" ] && source \"/etc/conf.d/${myservice}\"" >> TMPCACHE
+ print "" >> TMPCACHE
+ print " [ -e /etc/conf.d/net ] && \\" >> TMPCACHE
+ print " [ \"${myservice%%.*}\" = \"net\" ] && \\" >> TMPCACHE
+ print " [ \"${myservice##*.}\" != \"${myservice}\" ] && source /etc/conf.d/net" >> TMPCACHE
+ print "" >> TMPCACHE
+ print " [ -e /etc/rc.conf ] && source /etc/rc.conf" >> TMPCACHE
+ print "" >> TMPCACHE
+ print " depend() {" >> TMPCACHE
+ print " return 0" >> TMPCACHE
+ print " }" >> TMPCACHE
+ print "" >> TMPCACHE
+}
+
+function print_end() {
+ print "" >> TMPCACHE
+ print " depend" >> TMPCACHE
+ print ")" >> TMPCACHE
+ print "" >> TMPCACHE
+}
+
+BEGIN {
+
+ extension("/lib/rcscripts/filefuncs.so", "dlload")
+
+ # Get our environment variables
+ SVCDIR = ENVIRON["SVCDIR"]
+ if (SVCDIR == "") {
+ eerror("Could not get SVCDIR!")
+ exit 1
+ }
+
+ # Since this could be called more than once simultaneously, use a
+ # temporary cache and rename when finished. See bug 47111
+ ("/bin/mktemp "SVCDIR"/depcache.XXXXXXX") | getline TMPCACHE
+ if (TMPCACHE == "") {
+ eerror("Failed to create temporary cache!")
+ exit 1
+ }
+
+ pipe = "ls /etc/init.d/*"
+ while ((pipe | getline tmpstring) > 0)
+ scripts = scripts " " tmpstring
+ close(pipe)
+
+ split(scripts, TMPRCSCRIPTS)
+
+ # Make sure that its a file we are working with,
+ # and do not process scripts, source or backup files.
+ for (x in TMPRCSCRIPTS)
+ if (((isfile(TMPRCSCRIPTS[x])) || (islink(TMPRCSCRIPTS[x]))) &&
+ (TMPRCSCRIPTS[x] !~ /((\.(c|bak))|\~)$/)) {
+
+ RCCOUNT++
+
+ RCSCRIPTS[RCCOUNT] = TMPRCSCRIPTS[x]
+ }
+
+ if (RCCOUNT == 0) {
+ eerror("No scripts to process!")
+ dosystem("rm -f "TMPCACHE)
+ exit 1
+ }
+
+ print_start()
+
+ for (count = 1;count <= RCCOUNT;count++) {
+
+ MYFNR = 1
+ MYFILENAME = RCSCRIPTS[count]
+ STAT_DATA[1] = 1
+
+ while (((getline < (RCSCRIPTS[count])) > 0) && (!NEXTFILE)) {
+
+ # If line start with a '#' and is the first line
+ if (($0 ~ /^[[:space:]]*#/) && (MYFNR == 1)) {
+
+ # Remove any spaces and tabs
+ gsub(/[[:space:]]+/, "")
+
+ if ($0 == "#!/sbin/runscript") {
+
+ if (RCSCRIPTS[count] ~ /\.sh$/) {
+
+ ewarn(RCSCRIPTS[count] " is invalid (should not end with '.sh')")
+ NEXTFILE = 1
+ continue
+ }
+
+ if (stat(MYFILENAME, STAT_DATA) != 0)
+ ewarn("Could not stat \"" MYFILENAME "\"")
+
+ ISRCSCRIPT = 1
+ print_header1(STAT_DATA["mtime"])
+ } else {
+
+ NEXTFILE = 1
+ continue
+ }
+ }
+
+ # Filter out comments and only process if its a rcscript
+ if (($0 !~ /^[[:space:]]*#/) && (ISRCSCRIPT)) {
+
+ # If line contain 'depend()', set GOTDEPEND to 1
+ if ($0 ~ /depend[[:space:]]*\(\)/) {
+
+ GOTDEPEND = 1
+
+ print_header2()
+ print " # Actual depend() function ..." >> TMPCACHE
+ }
+
+ # We have the depend function...
+ if (GOTDEPEND) {
+
+ # Basic theory is that COUNT will be 0 when we
+ # have matching '{' and '}'
+ COUNT += gsub(/{/, "{")
+ COUNT -= gsub(/}/, "}")
+
+ # This is just to verify that we have started with
+ # the body of depend()
+ SBCOUNT += gsub(/{/, "{")
+
+ # Make sure depend() contain something, else bash
+ # errors out (empty function).
+ if ((SBCOUNT > 0) && (COUNT == 0))
+ print " \treturn 0" >> TMPCACHE
+
+ # Print the depend() function
+ print " " $0 >> TMPCACHE
+
+ # If COUNT=0, and SBCOUNT>0, it means we have read
+ # all matching '{' and '}' for depend(), so stop.
+ if ((SBCOUNT > 0) && (COUNT == 0)) {
+
+ GOTDEPEND = 0
+ COUNT = 0
+ SBCOUNT = 0
+ ISRCSCRIPT = 0
+
+ print_end()
+
+ NEXTFILE = 1
+ continue
+ }
+ }
+ }
+
+ MYFNR++
+ }
+
+ close(RCSCRIPTS[count])
+
+ NEXTFILE = 0
+
+ }
+
+
+ assert(dosystem("rm -f "SVCDIR"/depcache"), "system(rm -f "SVCDIR"/depcache)")
+ assert(dosystem("mv "TMPCACHE" "SVCDIR"/depcache"), "system(mv "TMPCACHE" "SVCDIR"/depcache)")
+}
+
+
+# vim:ts=4
diff --git a/src/awk/functions.awk b/src/awk/functions.awk
new file mode 100644
index 0000000..825daed
--- /dev/null
+++ b/src/awk/functions.awk
@@ -0,0 +1,156 @@
+# Copyright 1999-2004 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header$
+
+function einfo(string)
+{
+ printf(" %s %s%s", "\033[32;01m*\033[0m", string, "\n")
+}
+
+function ewarn(string)
+{
+ printf(" %s %s%s" , "\033[33;01m*\033[0m", string, "\n")
+}
+
+function eerror(string)
+{
+ printf(" %s %s%s" , "\033[31;01m*\033[0m", string, "\n")
+}
+
+function isfile(pathname, x, ret, data)
+{
+ ret = 0
+ data[1] = 1
+
+ if (pathname == "")
+ return 0
+
+ ret = stat(pathname, data)
+ if (ret < 0)
+ return 0
+
+ for (i in data) {
+ if (i == "type")
+ if (data[i] == "file")
+ ret = 1
+ }
+
+ return ret
+}
+
+function islink(pathname, x, ret, data)
+{
+ ret = 0
+ data[1] = 1
+
+ if (pathname == "")
+ return 0
+
+ ret = stat(pathname, data)
+ if (ret < 0)
+ return 0
+
+ for (i in data) {
+ if (i == "type")
+ if (data[i] == "symlink")
+ ret = 1
+ }
+
+ return ret
+}
+
+function isdir(pathname, x, ret, data)
+{
+ ret = 0
+ data[1] = 1
+
+ if (pathname == "")
+ return 0
+
+ ret = stat(pathname, data)
+ if (ret < 0)
+ return 0
+
+ for (i in data) {
+ if (i == "type")
+ if (data[i] == "directory")
+ ret = 1
+ }
+
+ return ret
+}
+
+function mktree(pathname, mode, x, max, ret, data, pathnodes, tmppath)
+{
+ ret = 0
+ data[1] = 1
+ pathnodes[1] = 1
+
+ if (pathname == "")
+ return 0
+
+ if (pathname ~ /^\//)
+ tmppath = ""
+ else
+ tmppath = "."
+
+ split(pathname, pathnodes, "/")
+
+ for (x in pathnodes)
+ max++
+
+ # We cannot use 'for (x in pathnodes)', as gawk likes to
+ # sort the order indexes are processed ...
+ for (x = 1;x <= max;x++) {
+ if (pathnodes[x] == "")
+ continue
+
+ tmppath = tmppath "/" pathnodes[x]
+
+ ret = stat(tmppath, data)
+ if (ret < 0)
+ if (mkdir(tmppath, mode) < 0)
+ return 0
+ }
+
+ return 1
+}
+
+# symlink() wrapper that normalize return codes ...
+function dosymlink(oldpath, newpath, ret)
+{
+ ret = 0
+
+ ret = symlink(oldpath, newpath)
+ if (ret < 0)
+ return 0
+ else
+ return 1
+}
+
+# system() wrapper that normalize return codes ...
+function dosystem(command, ret)
+{
+ ret = 0
+
+ ret = system(command)
+ if (ret == 0)
+ return 1
+ else
+ return 0
+}
+
+# assert --- assert that a condition is true. Otherwise exit.
+# This is from the gawk info manual.
+function assert(condition, string)
+{
+ if (! condition) {
+ printf("%s:%d: assertion failed: %s\n",
+ FILENAME, FNR, string) > "/dev/stderr"
+ _assert_exit = 1
+ exit 1
+ }
+}
+
+
+# vim:ts=4
diff --git a/src/awk/gendepends.awk b/src/awk/gendepends.awk
new file mode 100644
index 0000000..c4073b4
--- /dev/null
+++ b/src/awk/gendepends.awk
@@ -0,0 +1,562 @@
+# Copyright 1999-2004 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header$
+
+# bool check_service(name)
+#
+# Returns true if the service exists
+#
+function check_service(name, x)
+{
+ for (x = 1; x <= RC_NUMBER; x++) {
+ if (DEPTREE[x,NAME] == name)
+ return 1
+ }
+
+ return 0
+}
+
+# int get_service_index(name)
+#
+# Return the index position in DEPTREE
+#
+function get_service_index(name, x)
+{
+ for (x = 1; x <= RC_NUMBER; x++) {
+ if (DEPTREE[x,NAME] == name)
+ return x
+ }
+
+ return 0
+}
+
+# bool check_depend(service1, type, service2)
+#
+# Returns true if 'service1' need/use/is_before/is_after 'service2'
+#
+function check_depend(service1, type, service2, tmpsplit, x)
+{
+ if (check_service(service1)) {
+ x = get_service_index(service1)
+
+ if ((x,type) in DEPTREE) {
+ split(DEPTREE[x,type], tmpsplit)
+
+ for (x in tmpsplit) {
+ if (tmpsplit[x] == service2)
+ return 1
+ }
+ }
+ }
+
+ return 0
+}
+
+# bool check_resolved_depend(service1, type, service2)
+#
+# Returns true if 'service1' need/use/is_before/is_after 'service2'
+# It should only be trusted if we do the BEFORE/AFTER loop
+#
+function check_resolved_depend(service1, type, service2, tmpsplit, x)
+{
+ if (check_service(service1)) {
+ x = get_service_index(service1)
+
+ if ((x,type) in RESOLVED_DEPTREE) {
+ split(RESOLVED_DEPTREE[x,type], tmpsplit)
+
+ for (x in tmpsplit) {
+ if (tmpsplit[x] == service2)
+ return 1
+ }
+ }
+ }
+
+ return 0
+}
+
+# string get_resolved_depends(service, type)
+#
+# Return the services that depend of type on service
+# It should only be trusted if we do the BEFORE/AFTER loop
+#
+function get_resolved_depends(service, type, x)
+{
+ if (check_service(service)) {
+ x = get_service_index(service)
+
+ if ((x,type) in RESOLVED_DEPTREE)
+ return RESOLVED_DEPTREE[x,type]
+ }
+
+ return ""
+}
+
+# bool check_recursive_depend(service1, service2, bool checkuse)
+#
+# Return true if service1 USE/NEED a service that NEEDS/USES
+# service2
+# It should only be trusted if we do the BEFORE/AFTER loop
+#
+function check_recursive_depend(service1, service2, checkuse, x, deps, deplist)
+{
+ deps = get_resolved_depends(service2, NEEDME)
+ if (deps != "") {
+ split(deps, deplist)
+ for (x in deplist)
+ if (check_resolved_depend(service1, NEED, deplist[x]))
+ return 1
+ if (checkuse && check_resolved_depend(service1, USE, deplist[x]))
+ return 1
+ }
+
+ if (!checkuse)
+ return 0
+
+ deps = get_resolved_depends(service2, USEME)
+ if (deps != "") {
+ split(deps, deplist)
+ for (x in deplist)
+ if (check_resolved_depend(service1, NEED, deplist[x]) ||
+ check_resolved_depend(service1, USE, deplist[x])) {
+ return 1
+ }
+ }
+
+ return 0
+}
+
+# bool add_deptree_item(rcnumber, type, item)
+#
+# Add an item(s) 'item' to the DEPTREE array at index [rcnumber,type]
+#
+function add_deptree_item(rcnumber, type, item)
+{
+ if (DEPTREE[rcnumber,type] != "")
+ DEPTREE[rcnumber,type] = DEPTREE[rcnumber,type] " " item
+ else
+ DEPTREE[rcnumber,type] = item
+
+ return 1
+}
+
+# bool add_provide(service, provide)
+#
+# Add a name of a virtual service ('provide') that 'service' Provides
+#
+function add_provide(service, provide)
+{
+ # We cannot have a service Provide a virtual service with the same name as
+ # an existing service ...
+ if (check_service(provide)) {
+ eerror(" Cannot add provide '" provide "', as a service with the same name exists!")
+ return 0
+ }
+
+ if (check_provide(provide)) {
+ # We cannot have more than one service Providing a virtual ...
+ ewarn(" Service '" get_provide(provide) "' already provided by '" provide "'!;")
+ ewarn(" Not adding service '" service "'...")
+ # Do not fail here as we do have a service that resolves the virtual
+ } else {
+ # Sanity check
+ if (check_service(service)) {
+ PROVIDE_LIST[provide] = service
+ } else {
+ eerror(" Cannot add provide '" provide "', as service '" service "' does not exist!")
+ return 0
+ }
+ }
+
+ return 1
+}
+
+# string get_provide(provide)
+#
+# Return the name of the service that Provides 'provide'
+#
+function get_provide(provide)
+{
+ if (provide in PROVIDE_LIST)
+ if (check_service(PROVIDE_LIST[provide]))
+ return PROVIDE_LIST[provide]
+
+ return ""
+}
+
+# bool check_provide(provide)
+#
+# Return true if any service Provides the virtual service with name 'provide'
+#
+function check_provide(provide)
+{
+ if (provide in PROVIDE_LIST)
+ return 1
+
+ return 0
+}
+
+# bool add_db_entry(service, type, item)
+#
+# Add a entry to RESOLVED_DEPTREE
+#
+function add_db_entry(service, type, item, x, sindex, tmpsplit)
+{
+ if (!check_service(service)) {
+ eerror(" Service '" service "' do not exist!")
+ return 0
+ }
+
+ sindex = get_service_index(service)
+
+ if ((sindex,type) in RESOLVED_DEPTREE) {
+ split(RESOLVED_DEPTREE[sindex,type], tmpsplit)
+
+ for (x in tmpsplit) {
+ if (tmpsplit[x] == item)
+ return 1
+ }
+
+ RESOLVED_DEPTREE[sindex,type] = RESOLVED_DEPTREE[sindex,type] " " item
+ } else {
+ RESOLVED_DEPTREE[sindex,type] = item
+ }
+
+ return 1
+}
+
+# void resolve_depend(type, service, deplist)
+#
+# Verify a depend entry(s) 'deplist' for service 'service' of type 'type',
+# and then add it to the DB.
+#
+function resolve_depend(type, service, deplist, x, deparray)
+{
+ if ((type == "") || (service == "") || (deplist == ""))
+ return
+
+ # If there are no existing service 'service', resolve possible
+ # provided services
+ if (!check_service(service)) {
+ if (check_provide(service))
+ service = get_provide(service)
+ else
+ return
+ }
+
+ split(deplist, deparray)
+
+ for (x in deparray) {
+
+ # If there are no existing service 'deparray[x]', resolve possible
+ # provided services
+ if (!check_service(deparray[x])) {
+ if (check_provide(deparray[x]))
+ deparray[x] = get_provide(deparray[x])
+ }
+
+ # Handle 'need', as it is the only dependency type that
+ # should handle invalid database entries currently.
+ if (!check_service(deparray[x])) {
+
+ if (((type == NEED) || (type == NEEDME)) && (deparray[x] != "net")) {
+
+ ewarn(" Can't find service '" deparray[x] "' needed by '" service "'; continuing...")
+
+ # service is broken due to missing 'need' dependencies
+ add_db_entry(service, BROKEN, deparray[x])
+
+ continue
+ }
+ else if (deparray[x] != "net")
+ continue
+ }
+
+ # Ugly bug ... if a service depends on itself, it creates
+ # a 'mini fork bomb' effect, and breaks things...
+ if (deparray[x] == service) {
+
+ # Dont work too well with the '*' use and need
+ if ((type != BEFORE) && (type != AFTER))
+ ewarn(" Service '" deparray[x] "' can't depend on itself; continuing...")
+
+ continue
+ }
+
+ # Currently only these depend/order types are supported
+ if ((type == NEED) || (type == USE) || (type == BEFORE) || (type == AFTER)) {
+
+ if (type == BEFORE) {
+ # NEED and USE override BEFORE (service BEFORE deparray[x])
+ if (check_resolved_depend(service, NEED, deparray[x]) ||
+ check_resolved_depend(service, USE, deparray[x]))
+ continue
+
+ if (check_recursive_depend(service, deparray[x], 1)) {
+ ewarn(" Service '" service "' should be BEFORE service '" deparray[x] "', but one of")
+ ewarn(" the services '" service "' depends on, depends on '" deparray[x] "'!")
+ continue
+ }
+ }
+
+ if (type == AFTER) {
+ # NEED and USE override AFTER (service AFTER deparray[x])
+ if (check_resolved_depend(deparray[x], NEED, service) ||
+ check_resolved_depend(deparray[x], USE, service))
+ continue
+
+ if (check_recursive_depend(deparray[x], service, 1)) {
+ ewarn(" Service '" service "' should be AFTER service '" deparray[x] "', but one of")
+ ewarn(" the services '" deparray[x] "' depends on, depends on '" service "'!")
+ continue
+ }
+ }
+
+ # NEED override USE (service USE deparray[x])
+ if (type == USE && (check_resolved_depend(deparray[x], NEED, service) ||
+ check_recursive_depend(deparray[x], service, 0))) {
+ ewarn(" Service '" deparray[x] "' NEED service '" service "', but service '" service "' wants")
+ ewarn(" to USE service '" deparray[x] "'!")
+ continue
+ }
+
+ # We do not want to add circular depends ...
+ if (check_depend(deparray[x], type, service) ||
+ check_resolved_depend(deparray[x], type, service)) {
+
+ if ((service,deparray[x],type) in CIRCULAR_DEPEND)
+ continue
+
+ if ((deparray[x],service,type) in CIRCULAR_DEPEND)
+ continue
+
+ ewarn(" Services '" service "' and '" deparray[x] "' have circular")
+ ewarn(" dependency of type '" TYPE_NAMES[type] "'; continuing...")
+
+ CIRCULAR_DEPEND[service,deparray[x],type] = "yes"
+
+ continue
+ }
+
+ add_db_entry(service, type, deparray[x])
+
+ # Reverse mapping
+ if (type == NEED)
+ add_db_entry(deparray[x], NEEDME, service)
+
+ # Reverse mapping
+ if (type == USE)
+ add_db_entry(deparray[x], USEME, service)
+
+ # Reverse mapping
+ if (type == BEFORE)
+ add_db_entry(deparray[x], AFTER, service)
+
+ # Reverse mapping
+ if (type == AFTER)
+ add_db_entry(deparray[x], BEFORE, service)
+ }
+ }
+}
+
+BEGIN {
+ NAME = 1
+ RC_NUMBER = 0
+
+ # Types ...
+ NEED = 2
+ NEEDME = 3
+ USE = 4
+ USEME = 5
+ BEFORE = 6
+ AFTER = 7
+ BROKEN = 8
+ MTIME = 9
+ PROVIDE = 10 # Not part of Types as when finally printed ...
+ TYPES_MIN = 2
+ TYPES_MAX = 9
+
+ TYPE_NAMES[NEED] = "ineed"
+ TYPE_NAMES[NEEDME] = "needsme"
+ TYPE_NAMES[USE] = "iuse"
+ TYPE_NAMES[USEME] = "usesme"
+ TYPE_NAMES[BEFORE] = "ibefore"
+ TYPE_NAMES[AFTER] = "iafter"
+ TYPE_NAMES[BROKEN] = "broken"
+ TYPE_NAMES[PROVIDE] = "provide"
+ TYPE_NAMES[MTIME] = "mtime"
+
+ # Get our environment variables
+ SVCDIR = ENVIRON["SVCDIR"]
+ if (SVCDIR == "") {
+ eerror("Could not get SVCDIR!")
+ exit 1
+ }
+
+ # There we do not really use yet
+ DEPTYPES = ENVIRON["DEPTYPES"]
+ ORDTYPES = ENVIRON["ORDTYPES"]
+
+ #CACHEDTREE = SVCDIR "/deptree"
+ ORIGCACHEDTREE = SVCDIR "/deptree"
+
+ # Since this could be called more than once simultaneously, use a
+ # temporary cache and rename when finished. See bug 48303
+ ("/bin/mktemp "SVCDIR"/treecache.XXXXXXX") | getline CACHEDTREE
+ if (CACHEDTREE == "") {
+ eerror("Failed to create temporary cache!")
+ exit 1
+ }
+
+ # We remove it below now only before moving the temp one over.
+ #assert(dosystem("rm -f " CACHEDTREE ), "system(rm -f " CACHEDTREE ")")
+}
+
+{
+ #
+ # Build our DEPTREE array
+ #
+
+ if ($1 == "RCSCRIPT") {
+ RC_NUMBER++
+
+ DEPTREE[RC_NUMBER,NAME] = $2
+ }
+
+ if ($1 == "NEED") {
+ sub(/NEED[[:space:]]*/, "")
+
+ if ($0 != "")
+ add_deptree_item(RC_NUMBER, NEED, $0)
+ }
+
+ if ($1 == "USE") {
+ sub(/USE[[:space:]]*/, "")
+
+ if ($0 != "")
+ add_deptree_item(RC_NUMBER, USE, $0)
+ }
+
+ if ($1 == "BEFORE") {
+ sub(/BEFORE[[:space:]]*/, "")
+
+ if ($0 != "")
+ add_deptree_item(RC_NUMBER, BEFORE, $0)
+ }
+
+ if ($1 == "AFTER") {
+ sub(/AFTER[[:space:]]*/, "")
+
+ if ($0 != "")
+ add_deptree_item(RC_NUMBER, AFTER, $0)
+ }
+
+ if ($1 == "PROVIDE") {
+ sub(/PROVIDE[[:space:]]*/, "")
+
+ if ($0 != "")
+ add_deptree_item(RC_NUMBER, PROVIDE, $0)
+ }
+
+ if ($1 == "MTIME") {
+ sub(/MTIME[[:space:]]*/, "")
+
+ if ($0 != "") {
+ # We add this directly to RESOLVED_DEPTREE
+ add_db_entry(DEPTREE[RC_NUMBER,NAME], MTIME, $0)
+ }
+ }
+}
+
+END {
+ # Add the 'net' service if it do not exist ...
+ if (!check_service("net")) {
+ RC_NUMBER++
+ DEPTREE[RC_NUMBER,NAME] = "net"
+ }
+
+ # Calculate all the provides ...
+ for (x = 1;x <= RC_NUMBER;x++) {
+ if ((x,PROVIDE) in DEPTREE)
+ add_provide(DEPTREE[x,NAME], DEPTREE[x,PROVIDE])
+ }
+
+ # Now do NEED
+ for (x = 1;x <= RC_NUMBER;x++) {
+ if ((x,NEED) in DEPTREE)
+ resolve_depend(NEED, DEPTREE[x,NAME], DEPTREE[x,NEED])
+ }
+
+ # Now do USE
+ for (x = 1;x <= RC_NUMBER;x++) {
+ if ((x,USE) in DEPTREE)
+ resolve_depend(USE, DEPTREE[x,NAME], DEPTREE[x,USE])
+ }
+
+ # Now do BEFORE and AFTER
+ for (x = 1;x <= RC_NUMBER;x++) {
+
+ if ((x,BEFORE) in DEPTREE)
+ resolve_depend(BEFORE, DEPTREE[x,NAME], DEPTREE[x,BEFORE])
+
+ if ((x,AFTER) in DEPTREE)
+ resolve_depend(AFTER, DEPTREE[x,NAME], DEPTREE[x,AFTER])
+ }
+
+ for (x = TYPES_MIN; x <= TYPES_MAX; x++)
+ print "rc_type_" TYPE_NAMES[x] "=" x >> (CACHEDTREE)
+ print "rc_index_scale=" (TYPES_MAX + 1) >> (CACHEDTREE)
+ print "" >> (CACHEDTREE)
+ print "declare -a RC_DEPEND_TREE" >> (CACHEDTREE)
+ print "" >> (CACHEDTREE)
+ print "RC_DEPEND_TREE[0]=" RC_NUMBER >> (CACHEDTREE)
+ print "" >> (CACHEDTREE)
+
+ # Generate the resolved CACHEDTREE
+ #
+ # NOTE: We used to use depinfo_<scriptname>() function to resolve our
+ # rc_<type> variables, but that do not scale when the names of
+ # the scripts include invalid bash variable characters (+,.,etc).
+ #
+ for (x = 1;x <= RC_NUMBER;x++) {
+
+ print "RC_DEPEND_TREE[" (x * (TYPES_MAX + 1)) "]=\"" DEPTREE[x,NAME] "\"" >> (CACHEDTREE)
+
+ for (y = TYPES_MIN; y <= TYPES_MAX; y++) {
+
+ tmpname = "RC_DEPEND_TREE[" (x * (TYPES_MAX + 1)) "+" y "]"
+
+ if ((x,y) in RESOLVED_DEPTREE) {
+
+ split(RESOLVED_DEPTREE[x,y], tmparray1)
+ count = asort(tmparray1, tmparray2)
+ tmpstr = tmparray2[1]
+
+ for (i = 2;i <= count;i++)
+ tmpstr = tmpstr " " tmparray2[i]
+
+ print tmpname "=\"" tmpstr "\"" >> (CACHEDTREE)
+ } else
+ print tmpname "=" >> (CACHEDTREE)
+ }
+
+ print "" >> (CACHEDTREE)
+ }
+
+ # Do not export these, as we want them local
+ print "RC_GOT_DEPTREE_INFO=\"yes\"" >> (CACHEDTREE)
+ print "" >> (CACHEDTREE)
+
+ if (check_provide("logger"))
+ print "LOGGER_SERVICE=\"" get_provide("logger") "\"" >> (CACHEDTREE)
+ else
+ print "LOGGER_SERVICE=" >> (CACHEDTREE)
+
+ close(CACHEDTREE)
+
+ assert(dosystem("rm -f "ORIGCACHEDTREE), "system(rm -f "ORIGCACHEDTREE")")
+ assert(dosystem("mv "CACHEDTREE" "ORIGCACHEDTREE), "system(mv "CACHEDTREE" "ORIGCACHEDTREE")")
+}
+
+
+# vim:ts=4
diff --git a/src/awk/genenviron.awk b/src/awk/genenviron.awk
new file mode 100644
index 0000000..087bed3
--- /dev/null
+++ b/src/awk/genenviron.awk
@@ -0,0 +1,179 @@
+# Copyright 1999-2004 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header$
+
+BEGIN {
+
+ extension("/lib/rcscripts/filefuncs.so", "dlload")
+
+ # Get our environment variables
+ SVCDIR = ENVIRON["SVCDIR"]
+ if (SVCDIR == "") {
+ eerror("Could not get SVCDIR!")
+ exit 1
+ }
+
+ pipe = "ls -1 /etc/env.d/."
+ while ((pipe | getline tmpstring) > 0)
+ scripts = scripts " /etc/env.d/" tmpstring
+ close(pipe)
+
+ split(scripts, TMPENVFILES)
+
+ # Make sure that its a file we are working with,
+ # and do not process scripts, source or backup files.
+ # NOTE: do not use 'for (x in TMPENVFILES)', as gawk
+ # have this notion that it should mess with the
+ # order it list things then ....
+ for (x = 1;;x++) {
+
+ if (x in TMPENVFILES) {
+
+ if ((isfile(TMPENVFILES[x])) &&
+ (TMPENVFILES[x] !~ /((\.(sh|c|bak))|\~)$/)) {
+
+ ENVCOUNT++
+
+ ENVFILES[ENVCOUNT] = TMPENVFILES[x]
+ }
+ } else
+ break
+ }
+
+ if (ENVCOUNT == 0) {
+
+ eerror("No files to process!")
+ exit 1
+ }
+
+ ENVCACHE = SVCDIR "/envcache"
+ SHPROFILE = "/etc/profile.env"
+ CSHPROFILE = "/etc/csh.env"
+
+ # SPECIALS are treated differently. For each env.d file, the variables are
+ # appended seperated with a ':'. If not in specials, for each env.d file,
+ # the variable are just set to the new value.
+ tmpspecials="KDEDIRS:PATH:CLASSPATH:LDPATH:MANPATH:INFOPATH:ROOTPATH:CONFIG_PROTECT:CONFIG_PROTECT_MASK:PRELINK_PATH:PRELINK_PATH_MASK:PYTHONPATH:ADA_INCLUDE_PATH:ADA_OBJECTS_PATH"
+ split(tmpspecials, SPECIALS, ":")
+
+ unlink(ENVCACHE)
+
+ for (count = 1;count <= ENVCOUNT;count++) {
+
+ while ((getline < (ENVFILES[count])) > 0) {
+
+ # Filter out comments
+ if ($0 !~ /^[[:space:]]*#/) {
+
+ split($0, envnode, "=")
+
+ if (envnode[2] == "")
+ continue
+
+ if ($0 == "")
+ continue
+
+ # LDPATH should not be in environment
+ if (envnode[1] == "LDPATH")
+ continue
+
+ # In bash there should be no space between the variable name and
+ # the '=' ...
+ if (envnode[1] ~ /[^[:space:]]*[[:space:]]+$/)
+ continue
+
+ # strip variable name and '=' from data
+ sub("^[[:space:]]*" envnode[1] "[[:space:]]*=", "")
+ # strip all '"' and '\''
+ gsub(/\"/, "")
+ gsub(/\'/, "")
+ # strip leading and trailing spaces
+ gsub(/^[[:space:]]*/, "")
+ gsub(/[[:space:]]*$/, "")
+
+ if (envnode[1] in ENVTREE) {
+
+ DOSPECIAL = 0
+
+ for (x in SPECIALS) {
+
+ # Is this a special variable ?
+ if (envnode[1] == SPECIALS[x])
+ DOSPECIAL = 1
+ }
+
+ if (DOSPECIAL) {
+ split(ENVTREE[envnode[1]], tmpstr, ":")
+
+ # Check that we do not add dups ...
+ NODUPS = 1
+ for (x in tmpstr)
+ if (tmpstr[x] == $0)
+ NODUPS = 0
+
+ if (NODUPS)
+ # Once again, "CONFIG_PROTECT" and "CONFIG_PROTECT_MASK"
+ # are handled differently ...
+ if ((envnode[1] == "CONFIG_PROTECT") || (envnode[1] == "CONFIG_PROTECT_MASK"))
+ ENVTREE[envnode[1]] = ENVTREE[envnode[1]] " " $0
+ else
+ ENVTREE[envnode[1]] = ENVTREE[envnode[1]] ":" $0
+ } else
+ ENVTREE[envnode[1]] = $0
+ } else
+ ENVTREE[envnode[1]] = $0
+ }
+ }
+
+ close(ENVFILES[count])
+ }
+
+ for (x in ENVTREE)
+ print "export " x "=\"" ENVTREE[x] "\"" >> (ENVCACHE)
+
+ for (x in ENVTREE) {
+
+ # Print this a second time to make sure all variables
+ # are expanded ..
+ print "export " x "=\"" ENVTREE[x] "\"" >> (ENVCACHE)
+ print "echo \"" x "=${" x "}\"" >> (ENVCACHE)
+ }
+
+ close (ENVCACHE)
+
+ unlink(SHPROFILE)
+ unlink(CSHPROFILE)
+
+ # Add warning header for SHPROFILE
+ print "# THIS FILE IS AUTOMATICALLY GENERATED BY env-update." > (SHPROFILE)
+ print "# DO NOT EDIT THIS FILE. CHANGES TO STARTUP PROFILES" >> (SHPROFILE)
+ print "# GO INTO /etc/profile NOT /etc/profile.env" >> (SHPROFILE)
+ print "" >> (SHPROFILE)
+
+ # Add warning header for CSHPROFILE
+ print "# THIS FILE IS AUTOMATICALLY GENERATED BY env-update." > (CSHPROFILE)
+ print "# DO NOT EDIT THIS FILE. CHANGES TO STARTUP PROFILES" >> (CSHPROFILE)
+ print "# GO INTO /etc/csh.cshrc NOT /etc/csh.env" >> (CSHPROFILE)
+ print "" >> (CSHPROFILE)
+
+
+ pipe = "bash " ENVCACHE
+ while ((pipe | getline) > 0) {
+
+ sub(/=/, "='")
+ sub(/$/, "'")
+
+ print "export " $0 >> (SHPROFILE)
+
+ sub(/=/, " ")
+
+ print "setenv " $0 >> (CSHPROFILE)
+ }
+
+ close(pipe)
+ close(SHPROFILE)
+ close(CSHPROFILE)
+}
+
+
+# vim:ts=4
diff --git a/src/consoletype.c b/src/consoletype.c
new file mode 100644
index 0000000..5e5c45c
--- /dev/null
+++ b/src/consoletype.c
@@ -0,0 +1,38 @@
+/*
+ * consoletype.c
+ * simple app to figure out whether the current terminal
+ * is serial, console (vt), or remote (pty).
+ *
+ * Copyright 1999-2004 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ * $Header$
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include "headers.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned char twelve = 12;
+ int maj;
+ struct stat sb;
+
+ fstat(0, &sb);
+ maj = major(sb.st_rdev);
+ if (maj != 3 && (maj < 136 || maj > 143)) {
+#if defined(__linux__)
+ if (ioctl (0, TIOCLINUX, &twelve) < 0) {
+ printf("serial\n");
+ return 1;
+ }
+#endif
+ printf("vt\n");
+ return 0;
+ } else {
+ printf("pty\n");
+ return 2;
+ }
+}
diff --git a/src/core/.cvsignore b/src/core/.cvsignore
new file mode 100644
index 0000000..8ef5509
--- /dev/null
+++ b/src/core/.cvsignore
@@ -0,0 +1,3 @@
+*.o
+depscan
+test-regex
diff --git a/src/core/ChangeLog b/src/core/ChangeLog
new file mode 100644
index 0000000..0ac157a
--- /dev/null
+++ b/src/core/ChangeLog
@@ -0,0 +1,163 @@
+# ChangeLog for Gentoo System Intialization core utilities.
+# Copyright 1999-2005 Gentoo Foundation; Distributed under the GPLv2
+# $Header$
+
+26 Jul 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * depend.c
+ * depend.h
+ * parse.c
+ * parse.h: Remove the "parallel" stuff, as we do not use it anymore.
+
+15 Apr 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * parse.c: Do not source rc.conf for every script - once is enough.
+
+14 Apr 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * depscan.c: Update error comments for stage name changes some time
+ back.
+
+ * parse.c,
+ * parse.h: Do not try to extract the depend() function from the
+ scripts, but rather source the whole file. This way we can detect
+ syntax errors, etc. Little bit slower, but not much.
+
+07 Apr 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * test-regex.c: Add two more tests.
+
+ * depscan.c
+ * misc.c
+ * misc.h: Add basic klibc support. I need to add a mkstemp
+ implementation to get it done properly.
+
+12 Mar 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * Makefile: Also remove the tests in the clean target.
+
+11 Mar 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * test-regex.c: Add a few strings and patterns. Enable tests to
+ specify if they should fail or pass.
+
+10 Mar 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * test-regex.c: New file
+ * Makefile: Add check target to compile and run tests
+ * simple-regex.c (__match_wildcard): Get recursion right so that we
+ do not match a wildcard _and_ inc data_p if there are still other
+ wildcards (?, *) that could match.
+
+ * Makefile
+ * depend.c
+ * simple-regex.c: Override the debug/warning CFLAGS. Kill a few
+ warnings.
+
+23 Feb 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * misc.c: Fix memory leak in mktree().
+
+18 Feb 2005 Martin Schlemmer <azarah@gentoo.org>
+
+ * Makefile: Add -fbounds-checking support when DEBUG=1.
+
+ * misc.h: Scrap STRING_LIST_FOR_EACH_SAFE() and recode from scratch
+ fixing invalid pointer operations.
+
+ * misc.c: Remove the last strlen() from strndup() that caused an
+ overrun.
+
+17 Feb 2005 Martin Schlemmer <azarah@gentoo.org>:
+
+ * misc.c: Fix overrun in strndup(), thanks to report from
+ Ned Ludd <solar@gentoo.org>.
+
+ * debug.h
+ * misc.h
+ * simple-regex.c: Print debug/errors to stderr, patch from
+ Ned Ludd <solar@gentoo.org>.
+
+ * debug.h: Replace invalid EXIT_FAILSTATUS with EXIT_FAILURE.
+
+ * parse.c: Modify parse_print_body() to be more ash friendly.
+ Suggestions from Ned Ludd <solar@gentoo.org>.
+
+ * debug.h: Remove the 'errno = ESPIPE' in DBG_MSG() for now, as it
+ seems to be fixed by the select() changes.
+
+ * parse.c: Disable write select() for now, as it is not needed.
+
+ * depscan.c: Only print EINFO msg if we actually update the cache.
+
+ * parse.c: Rename write_output() macro to PRINT_TO_BUFFER().
+
+ * parse.c
+ * parse.h: Rewrote large parts of generate_stage[12]() and their
+ machanics to use select() when writing to the pipes. This fixes a
+ buffering issue where too much data would cause the write to be
+ truncated, and the read pipe would then wait forever.
+
+ * misc.c: Fix gbasename() to compile under gcc-2.95.3.
+
+ * parse.c: Switch to stdio based io for reading pipes in
+ generate_stage2().
+
+ * misc.c
+ * misc.h: Add gbasename() that is similar to GNU's basename().
+
+ * parse.c: Use gbasename() instead of POSIX version.
+
+ * parse.c: Fix write_legacy_stage3() to quote the mtime in its output.
+
+ * misc.c
+ * parse.c: Change type of length from int to size_t to avoid warnings
+ when compiled for darwin.
+
+ * misc.c
+ misc.h: Add strndup() instead of relying on glibc's implementation
+ (should fix some issues on bsd and darwin).
+
+ * depend.c
+ * simple-regex.c: Do not define _GNU_SOURCE, but rather use our
+ strndup() from misc.h.
+
+ * parse.c: Do not define _GNU_SOURCE, but rather use our strndup() from
+ misc.h. Also change all usage of basename() to conform to POSIX, and
+ do not use the GNU variants.
+
+16 Feb 2005; Martin Schlemmer <azarah@gentoo.org>:
+
+ * depscan.c: Add uid check and quit if user is not root.
+
+ * depend.c
+ * depend.h: Change service_type_names declaration in depend.h to extern
+ and move the definition to depend.c to avoid warnings.
+
+ * README: New file.
+
+ * depscan.c: Add delete_var_dirs() to delete volatile directories in
+ svcdir. Change 'char *' declarations for create_directory() and
+ create_var_dirs() to 'const char*'.
+
+ * misc.h
+ * misc.c: Add rmtree() function. Fix ls_dir() not to include '.' and
+ '..' in its listing. Fix segfault in ls_dir() if the file list is
+ empty.
+
+ Add Header tags to all source files and Makefile.
+
+ * debug.h: perror() set errno to ESPIPE for some reason - restore errno
+ after calling perror().
+
+ * depscan.c: Add code to create svcdir and co if missing.
+
+ * misc.c: Add missing '\n' to DBG_MSG in mktree().
+
+ * misc.h: Add a comment about strcatpaths() allocating the memory needed.
+
+ Initial checkin. Still rough in some parts, but should be 100% similar
+ in output to depscan.sh and co.
+
+
+# vim:expandtab
diff --git a/src/core/Makefile b/src/core/Makefile
new file mode 100644
index 0000000..1e9c050
--- /dev/null
+++ b/src/core/Makefile
@@ -0,0 +1,81 @@
+# Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+#
+# 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.,
+# 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Header$
+
+CC = gcc
+override CFLAGS += -Wall
+EXTRA_CFLAGS = -DLEGACY_DEPSCAN
+STRIP = strip
+
+DEPSCAN = depscan
+TEST_REGEX = test-regex
+
+TARGETS = $(DEPSCAN)
+CHECK_TARGETS = $(TEST_REGEX)
+
+all: $(TARGETS)
+
+.ALL: all
+
+OBJS = \
+ parse.o \
+ depend.o \
+ simple-regex.o \
+ misc.o
+
+HEADERS = \
+ parse.h \
+ depend.h \
+ simple-regex.h \
+ misc.h \
+ debug.h
+
+
+# cc-option (from linux kernel sources)
+# Usage: cflags-y += $(call gcc-option, -march=winchip-c6, -march=i586)
+
+cc-option = $(shell if $(CC) $(1) -S -o /dev/null -xc /dev/null \
+ > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
+
+
+ifeq ($(DEBUG),1)
+ override CFLAGS += -ggdb3
+ override CFLAGS += $(call cc-option, -fbounds-checking, -pipe)
+ EXTRA_CFLAGS += -DRC_DEBUG
+endif
+
+$(DEPSCAN): $(OBJS) $(DEPSCAN).o
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -o $@ $^
+
+$(TEST_REGEX): $(TEST_REGEX).o simple-regex.o
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -o $@ $^
+
+$(OBJS): $(HEADERS)
+
+.c.o:
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<
+
+check: $(CHECK_TARGETS)
+ @for x in $^; do \
+ ./$${x} || exit 1; \
+ done
+
+strip: $(TARGETS)
+ $(STRIP) -s --remove-section=.note --remove-section=.comment $(TARGETS)
+
+clean:
+ rm -f *.o $(TARGETS) $(CHECK_TARGETS)
diff --git a/src/core/README b/src/core/README
new file mode 100644
index 0000000..31f4ef5
--- /dev/null
+++ b/src/core/README
@@ -0,0 +1,15 @@
+* Introduction
+==============
+
+This is still fairly alpha code, although I have tried to regression test it
+fairly extensively. After many requests, I have added it to CVS, so please
+do understand that it still is work in progress, although it should duplicate
+depscan.sh in functionality.
+
+To all those that might dabble with this, please send comments or fixes my
+way, and if I can ask very nicely, please do not commit before checking with
+me first, as I do have some large changes and additions still in the pipeline.
+
+
+Martin Schlemmer
+<azarah@gentoo.org>
diff --git a/src/core/debug.h b/src/core/debug.h
new file mode 100644
index 0000000..bc40038
--- /dev/null
+++ b/src/core/debug.h
@@ -0,0 +1,85 @@
+/*
+ * debug.h
+ *
+ * Simle debugging/logging macro's and functions.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#ifndef _DEBUG_H
+#define _DEBUG_H
+
+#if defined(RC_DEBUG)
+# define DBG_MSG(_format, _arg...) \
+ do { \
+ int old_errno = errno; \
+ fprintf(stderr, "DEBUG(1): in %s, function %s(), line %i:\n", __FILE__, \
+ __FUNCTION__, __LINE__); \
+ fprintf(stderr, "DEBUG(2): " _format, ## _arg); \
+ errno = old_errno; \
+ if (0 != errno) { \
+ perror("DEBUG(3)"); \
+ /* perror() for some reason sets errno to ESPIPE */ \
+ errno = old_errno; \
+ } \
+ } while (0)
+#else
+# define DBG_MSG(_format, _arg...) \
+ do { \
+ int old_errno = errno; \
+ /* Bit of a hack, as how we do things tend to cause seek
+ * errors when reading the parent/child pipes */ \
+ /* if ((0 != errno) && (ESPIPE != errno)) { */ \
+ if (0 != errno) { \
+ fprintf(stderr, "DEBUG(1): in %s, function %s(), line %i:\n", \
+ __FILE__, __FUNCTION__, __LINE__); \
+ fprintf(stderr, "DEBUG(2): " _format, ## _arg); \
+ errno = old_errno; \
+ perror("DEBUG(3)"); \
+ /* perror() for some reason sets errno to ESPIPE */ \
+ errno = old_errno; \
+ } \
+ } while (0)
+#endif
+
+#define FATAL_ERROR() \
+ do { \
+ int old_errno = errno; \
+ fprintf(stderr, "ERROR: file '%s', function '%s', line %i.\n", \
+ __FILE__, __FUNCTION__, __LINE__); \
+ errno = old_errno; \
+ if (0 != errno) \
+ perror("ERROR"); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+#define NEG_FATAL_ERROR(_x) \
+ do { \
+ if (-1 == _x) \
+ FATAL_ERROR(); \
+ } while (0)
+
+#define NULL_FATAL_ERROR(_x) \
+ do { \
+ if (NULL == _x) \
+ FATAL_ERROR(); \
+ } while (0)
+
+#endif /* _DEBUG_H */
+
diff --git a/src/core/depend.c b/src/core/depend.c
new file mode 100644
index 0000000..8fc6056
--- /dev/null
+++ b/src/core/depend.c
@@ -0,0 +1,532 @@
+/*
+ * depend.c
+ *
+ * Dependancy engine for Gentoo style rc-scripts.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "debug.h"
+#include "depend.h"
+#include "list.h"
+#include "misc.h"
+
+LIST_HEAD(service_info_list);
+
+/* Names for service types (service_type_t) in depend.h.
+ * Note that this should sync with service_type_t */
+char *service_type_names[] = {
+ "NEED",
+ "NEED_ME",
+ "USE",
+ "USE_ME",
+ "BEFORE",
+ "AFTER",
+ "BROKEN",
+ "PROVIDE",
+ NULL
+};
+
+int __service_resolve_dependency(char *servicename, char *dependency, service_type_t type);
+
+service_info_t *service_get_info(char *servicename) {
+ service_info_t *info;
+
+ if ((NULL == servicename) || (0 == strlen(servicename))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return NULL;
+ }
+
+ list_for_each_entry(info, &service_info_list, node) {
+ if (NULL != info->name)
+ if (0 == strcmp(info->name, servicename))
+ return info;
+ }
+
+ /* We use this to check if a service exists, so rather do not
+ * add debugging, otherwise it is very noisy! */
+ /* DBG_MSG("Invalid service name '%s'!\n", servicename); */
+
+ return NULL;
+}
+
+int service_add(char *servicename) {
+ service_info_t *info;
+ service_info_t *sorted;
+ int count;
+
+ if ((NULL == servicename) || (0 == strlen(servicename))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ info = service_get_info(servicename);
+ if (NULL == info) {
+ DBG_MSG("Adding service '%s'.\n", servicename);
+
+ info = malloc(sizeof(service_info_t));
+ if (NULL == info) {
+ DBG_MSG("Failed to allocate service_info_t!\n");
+ return -1;
+ }
+
+ info->name = strndup(servicename, strlen(servicename));
+ if (NULL == info->name) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ free(info);
+ return -1;
+ }
+
+ for (count = 0; count < ALL_SERVICE_TYPE_T; count++)
+ info->depend_info[count] = NULL;
+ info->provide = NULL;
+
+ /* We want to keep the list sorted */
+ list_for_each_entry(sorted, &service_info_list, node) {
+ if (strcmp(sorted->name, servicename) > 0) {
+ break;
+ }
+ }
+
+ list_add_tail(&info->node, &sorted->node);
+
+ return 0;
+ } else {
+ DBG_MSG("Tried to add duplicate service '%s'!\n", servicename);
+ }
+
+ return -1;
+}
+
+int service_is_dependency(char *servicename, char *dependency, service_type_t type) {
+ service_info_t *info;
+ char *service;
+ int count = 0;
+
+ if ((NULL == servicename) || (0 == strlen(servicename)) ||
+ (NULL == dependency) || (0 == strlen(dependency))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ info = service_get_info(servicename);
+ if (NULL != info) {
+ STRING_LIST_FOR_EACH(info->depend_info[type], service, count) {
+ if (0 == strcmp(dependency, service))
+ return 0;
+ }
+ } else {
+ DBG_MSG("Invalid service name '%s'!\n", servicename);
+ }
+
+ return -1;
+}
+
+int service_add_dependency(char *servicename, char *dependency, service_type_t type) {
+ service_info_t *info;
+ char *tmp_buf;
+
+ if ((NULL == servicename) || (0 == strlen(servicename)) ||
+ (NULL == dependency) || (0 == strlen(dependency))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ info = service_get_info(servicename);
+ if (NULL != info) {
+ /* Do not add duplicates */
+ if (-1 == service_is_dependency(servicename, dependency, type)) {
+ DBG_MSG("Adding dependency '%s' of service '%s', type '%s'.\n",
+ dependency, servicename, service_type_names[type]);
+
+ tmp_buf = strndup(dependency, strlen(dependency));
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return -1;
+ }
+
+ STRING_LIST_ADD_SORT(info->depend_info[type], tmp_buf, error);
+ } else {
+ DBG_MSG("Duplicate dependency '%s' for service '%s', type '%s'!\n",
+ dependency, servicename,
+ service_type_names[type]);
+ /* Rather do not fail here, as we add a lot of doubles
+ * during resolving of dependencies */
+ }
+
+ return 0;
+ } else {
+ DBG_MSG("Invalid service name '%s'!\n", servicename);
+ }
+
+error:
+ return -1;
+}
+
+int service_del_dependency(char *servicename, char *dependency, service_type_t type) {
+ service_info_t *info;
+
+ if ((NULL == servicename) || (0 == strlen(servicename)) ||
+ (NULL == dependency) || (0 == strlen(dependency))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ if (-1 == service_is_dependency(servicename, dependency, type)) {
+ DBG_MSG("Tried to remove invalid dependency '%s'!\n", dependency);
+ return -1;
+ }
+
+ info = service_get_info(servicename);
+ if (NULL != info) {
+ DBG_MSG("Removing dependency '%s' of service '%s', type '%s'.\n",
+ dependency , servicename, service_type_names[type]);
+
+ STRING_LIST_DEL(info->depend_info[type], dependency, error);
+ return 0;
+ } else {
+ DBG_MSG("Invalid service name '%s'!\n", servicename);
+ }
+
+error:
+ return -1;
+}
+
+service_info_t *service_get_virtual(char *virtual) {
+ service_info_t *info;
+
+ if ((NULL == virtual) || (0 == strlen(virtual))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return NULL;
+ }
+
+ list_for_each_entry(info, &service_info_list, node) {
+ if (NULL != info->provide)
+ if (0 == strcmp(info->provide, virtual))
+ return info;
+ }
+
+ /* We use this to check if a virtual exists, so rather do not
+ * add debugging, otherwise it is very noisy! */
+ /* DBG_MSG("Invalid service name '%s'!\n", virtual); */
+
+ return NULL;
+}
+
+int service_add_virtual(char *servicename, char* virtual) {
+ service_info_t *info;
+
+ if ((NULL == servicename) || (0 == strlen(servicename)) ||
+ (NULL == virtual ) || (0 == strlen(virtual))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ if (NULL != service_get_info(virtual)) {
+ EERROR(" Cannot add provide '%s', as a service with the same name exists!\n",
+ virtual);
+ /* Do not fail here as we do have a service that resolves
+ * the virtual */
+ }
+
+ info = service_get_virtual(virtual);
+ if (NULL != info) {
+ /* We cannot have more than one service Providing a virtual */
+ EWARN(" Service '%s' already provides '%s'!;\n",
+ info->name, virtual);
+ EWARN(" Not adding service '%s'...\n", servicename);
+ /* Do not fail here as we do have a service that resolves
+ * the virtual */
+ } else {
+ info = service_get_info(servicename);
+ if (NULL != info) {
+ DBG_MSG("Adding virtual '%s' of service '%s'.\n",
+ virtual, servicename);
+
+ info->provide = strndup(virtual, strlen(virtual));
+ if (NULL == info->provide) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return -1;
+ }
+ } else {
+ DBG_MSG("Invalid service name '%s'!\n", servicename);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int service_set_mtime(char *servicename, time_t mtime) {
+ service_info_t *info;
+
+ if ((NULL == servicename) || (0 == strlen(servicename))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ info = service_get_info(servicename);
+ if (NULL != info) {
+ DBG_MSG("Setting mtime '%li' of service '%s'.\n",
+ mtime, servicename);
+
+ info->mtime = mtime;
+
+ return 0;
+ } else {
+ DBG_MSG("Invalid service name '%s'!\n", servicename);
+ }
+
+ return -1;
+}
+
+int __service_resolve_dependency(char *servicename, char *dependency, service_type_t type) {
+ service_info_t *info;
+ int retval;
+
+ if ((NULL == servicename) || (0 == strlen(servicename)) ||
+ (NULL == dependency) || (0 == strlen(dependency))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return -1;
+ }
+
+ info = service_get_info(servicename);
+ if (NULL == info) {
+ DBG_MSG("Invalid service name passed!\n");
+ return -1;
+ }
+
+ DBG_MSG("Checking dependency '%s' of service '%s', type '%s'.\n",
+ dependency, servicename, service_type_names[type]);
+
+ /* If there are no existing service 'dependency', try to resolve
+ * possible virtual services */
+ info = service_get_info(dependency);
+ if (NULL == info) {
+ info = service_get_virtual(dependency);
+ if (NULL != info) {
+ DBG_MSG("Virtual '%s' -> '%s' for service '%s', type '%s'.\n",
+ dependency, info->name, servicename,
+ service_type_names[type]);
+
+ retval = service_del_dependency(servicename, dependency, type);
+ if (-1 == retval) {
+ DBG_MSG("Failed to delete dependency!\n");
+ return -1;
+ }
+
+ /* Add the actual service name for the virtual */
+ dependency = info->name;
+ retval = service_add_dependency(servicename, dependency, type);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency!\n");
+ return -1;
+ }
+ }
+ }
+
+ /* Handle 'need', as it is the only dependency type that should
+ * handle invalid database entries currently. */
+ if (NULL == info) {
+ if ((type == NEED) || (type == NEED_ME)) {
+ EWARN(" Can't find service '%s' needed by '%s'; continuing...\n",
+ dependency, servicename);
+
+ retval = service_add_dependency(servicename, dependency, BROKEN);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency!\n");
+ return -1;
+ }
+
+ /* Delete invalid entry */
+ goto remove;
+ }
+
+ /* For the rest, if the dependency is not 'net', just silently
+ * die without error. Should not be needed as we add a 'net'
+ * service manually before we start, but you never know ... */
+ if (0 != strcmp(dependency, "net")) {
+ /* Delete invalid entry */
+ goto remove;
+ }
+ }
+
+ /* Ugly bug ... if a service depends on itself, it creates a
+ * 'mini fork bomb' effect, and breaks things horribly ... */
+ if (0 == strcmp(servicename, dependency)) {
+ /* Dont work too well with the '*' before and after */
+ if ((type != BEFORE) && (type != AFTER))
+ EWARN(" Service '%s' can't depend on itself; continuing...\n",
+ servicename);
+
+ /* Delete invalid entry */
+ goto remove;
+ }
+
+ /* Currently only these depend/order types are supported */
+ if ((type == NEED) || (type == USE) || (type == BEFORE) || (type == AFTER)) {
+ if (type == BEFORE) {
+ /* NEED and USE override BEFORE
+ * ('servicename' BEFORE 'dependency') */
+ if ((0 == service_is_dependency(servicename, dependency, NEED)) ||
+ (0 == service_is_dependency(servicename, dependency, USE))) {
+ /* Delete invalid entry */
+ goto remove;
+ }
+ }
+
+ if (type == AFTER) {
+ /* NEED and USE override AFTER
+ * ('servicename' AFTER 'dependency') */
+ if ((0 == service_is_dependency(dependency, servicename, NEED)) ||
+ (0 == service_is_dependency(dependency, servicename, USE))) {
+ /* Delete invalid entry */
+ goto remove;
+ }
+ }
+
+ /* We do not want to add circular dependencies ... */
+ if (0 == service_is_dependency(dependency, servicename, type)) {
+ EWARN(" Services '%s' and '%s' have circular\n",
+ servicename, dependency);
+ EWARN(" dependency of type '%s'; continuing...\n",
+ service_type_names[type]);
+
+ /* For now remove this dependency */
+ goto remove;
+ }
+
+ /* Reverse mapping */
+ if (type == NEED) {
+ retval = service_add_dependency(dependency, servicename, NEED_ME);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency!\n");
+ return -1;
+ }
+ }
+
+ /* Reverse mapping */
+ if (type == USE) {
+ retval = service_add_dependency(dependency, servicename, USE_ME);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency!\n");
+ return -1;
+ }
+ }
+
+ /* Reverse mapping */
+ if (type == BEFORE) {
+ retval = service_add_dependency(dependency, servicename, AFTER);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency!\n");
+ return -1;
+ }
+ }
+
+ /* Reverse mapping */
+ if (type == AFTER) {
+ retval = service_add_dependency(dependency, servicename, BEFORE);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency!\n");
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+
+remove:
+ /* Delete invalid entry */
+ DBG_MSG("Removing invalid dependency '%s' of service '%s', type '%s'.\n",
+ dependency, servicename, service_type_names[type]);
+
+ retval = service_del_dependency(servicename, dependency, type);
+ if (-1 == retval) {
+ DBG_MSG("Failed to delete dependency!\n");
+ return -1;
+ }
+
+ /* Here we should not die with error */
+ return 0;
+}
+
+int service_resolve_dependencies(void) {
+ service_info_t *info;
+ char *service;
+ char *next = NULL;
+ int count;
+
+ /* Add our 'net' service */
+ if (NULL == service_get_info("net")) {
+ if (-1 == service_add("net"))
+ return -1;
+ }
+
+ /* Calculate all virtuals */
+ list_for_each_entry(info, &service_info_list, node) {
+ STRING_LIST_FOR_EACH_SAFE(info->depend_info[PROVIDE], service, next, count)
+ if (-1 == service_add_virtual(info->name, service)) {
+ DBG_MSG("Failed to add virtual!\n");
+ return -1;
+ }
+ }
+
+ /* Now do NEED, USE, BEFORE and AFTER */
+ list_for_each_entry(info, &service_info_list, node) {
+ STRING_LIST_FOR_EACH_SAFE(info->depend_info[NEED], service, next, count) {
+ if (-1 == __service_resolve_dependency(info->name, service, NEED)) {
+ DBG_MSG("Failed to resolve dependency!\n");
+ return -1;
+ }
+ }
+ }
+ list_for_each_entry(info, &service_info_list, node) {
+ STRING_LIST_FOR_EACH_SAFE(info->depend_info[USE], service, next, count) {
+ if (-1 == __service_resolve_dependency(info->name, service, USE)) {
+ DBG_MSG("Failed to resolve dependency!\n");
+ return -1;
+ }
+ }
+ }
+ list_for_each_entry(info, &service_info_list, node) {
+ STRING_LIST_FOR_EACH_SAFE(info->depend_info[BEFORE], service, next, count) {
+ if (-1 == __service_resolve_dependency(info->name, service, BEFORE)) {
+ DBG_MSG("Failed to resolve dependency!\n");
+ return -1;
+ }
+ }
+ }
+ list_for_each_entry(info, &service_info_list, node) {
+ STRING_LIST_FOR_EACH_SAFE(info->depend_info[AFTER], service, next, count) {
+ if (-1 == __service_resolve_dependency(info->name, service, AFTER)) {
+ DBG_MSG("Failed to resolve dependency!\n");
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/core/depend.h b/src/core/depend.h
new file mode 100644
index 0000000..2a7ff77
--- /dev/null
+++ b/src/core/depend.h
@@ -0,0 +1,75 @@
+/*
+ * depend.h
+ *
+ * Dependancy engine for Gentoo style rc-scripts.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#ifndef _DEPEND_H
+#define _DEPEND_H
+
+#include <sys/types.h>
+#include "list.h"
+
+/* Dependency types supported or still to be implemented */
+typedef enum {
+ NEED, /* All dependencies needed by specified service */
+ NEED_ME, /* All dependencies that need specified service */
+ USE, /* All dependencies used by specified service */
+ USE_ME, /* All dependencies that use specified service */
+ BEFORE, /* All services started before specified service */
+ AFTER, /* All services started after specified service */
+ BROKEN, /* All dependencies of type NEED missing for
+ specified service */
+ PROVIDE, /* All virtual services provided by specified service */
+ ALL_SERVICE_TYPE_T
+} service_type_t;
+
+/* Names for above service types (service_type_t).
+ * Note that this should sync with above service_type_t */
+extern char *service_type_names[];
+
+typedef struct {
+ struct list_head node;
+
+ char *name; /* Name of service */
+ char **depend_info[ALL_SERVICE_TYPE_T]; /* String lists for each service
+ type */
+ char *provide; /* Name of virtual service it
+ provides. This is only valid
+ after we resolving - thus after
+ service_resolve_dependencies() */
+ time_t mtime; /* Modification time of script */
+} service_info_t;
+
+struct list_head service_info_list;
+
+service_info_t *service_get_info(char *servicename);
+int service_add(char *servicename);
+int service_is_dependency(char *servicename, char *dependency, service_type_t type);
+int service_add_dependency(char *servicename, char *dependency, service_type_t type);
+int service_del_dependency(char *servicename, char *dependency, service_type_t type);
+service_info_t *service_get_virtual(char *virtual);
+int service_add_virtual(char *servicename, char* virtual);
+int service_set_mtime(char *servicename, time_t mtime);
+int service_resolve_dependencies(void);
+
+#endif /* _DEPEND_H */
+
diff --git a/src/core/depscan.c b/src/core/depscan.c
new file mode 100644
index 0000000..5030eb4
--- /dev/null
+++ b/src/core/depscan.c
@@ -0,0 +1,297 @@
+/*
+ * depscan.c
+ *
+ * Basic frontend for updating the dependency cache.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#include <errno.h>
+#ifndef __KLIBC__
+# include <locale.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "depend.h"
+#include "misc.h"
+#include "parse.h"
+
+char* svcdir_subdirs[] = {
+ "softscripts",
+ "snapshot",
+ "options",
+ "started",
+ "starting",
+ "inactive",
+ "stopping",
+ NULL
+};
+
+char *svcdir_volatile_subdirs[] = {
+ "snapshot",
+ "broken",
+ NULL
+};
+
+int create_directory(const char *name);
+int create_var_dirs(const char *svcdir);
+int delete_var_dirs(const char *svcdir);
+
+int create_directory(const char *name) {
+ if ((NULL == name) || (0 == strlen(name))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check if directory exist, and is not a symlink */
+ if (!is_dir(name, 0)) {
+ if (exists(name)) {
+ /* Remove it if not a directory */
+ if (-1 == unlink(name)) {
+ DBG_MSG("Failed to remove '%s'!\n", name);
+ return -1;
+ }
+ }
+ /* Now try to create the directory */
+ if (-1 == mktree(name, 0755)) {
+ DBG_MSG("Failed to create '%s'!\n", name);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int create_var_dirs(const char *svcdir) {
+ char *tmp_path = NULL;
+ int i = 0;
+
+ if ((NULL == svcdir) || (0 == strlen(svcdir))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check and create svcdir if needed */
+ if (-1 == create_directory(svcdir)) {
+ DBG_MSG("Failed to create '%s'!\n", svcdir);
+ return -1;
+ }
+
+ while (NULL != svcdir_subdirs[i]) {
+ tmp_path = strcatpaths(svcdir, svcdir_subdirs[i]);
+ if (NULL == tmp_path) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return -1;
+ }
+
+ /* Check and create all the subdirs if needed */
+ if (-1 == create_directory(tmp_path)) {
+ DBG_MSG("Failed to create '%s'!\n", tmp_path);
+ free(tmp_path);
+ return -1;
+ }
+
+ free(tmp_path);
+ i++;
+ }
+
+ return 0;
+}
+
+int delete_var_dirs(const char *svcdir) {
+ char *tmp_path = NULL;
+ int i = 0;
+
+ if ((NULL == svcdir) || (0 == strlen(svcdir))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Just quit if svcdir do not exist */
+ if (!exists(svcdir)) {
+ DBG_MSG("'%s' does not exist!\n", svcdir);
+ return 0;
+ }
+
+ while (NULL != svcdir_volatile_subdirs[i]) {
+ tmp_path = strcatpaths(svcdir, svcdir_volatile_subdirs[i]);
+ if (NULL == tmp_path) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return -1;
+ }
+
+ /* Skip the directory if it does not exist */
+ if (!exists(tmp_path))
+ goto _continue;
+
+ /* Check and delete all files and sub directories if needed */
+ if (-1 == rmtree(tmp_path)) {
+ DBG_MSG("Failed to delete '%s'!\n", tmp_path);
+ free(tmp_path);
+ return -1;
+ }
+
+_continue:
+ free(tmp_path);
+ i++;
+ }
+
+ return 0;
+}
+
+#if defined(LEGACY_DEPSCAN)
+
+int main() {
+ FILE *cachefile_fd = NULL;
+ char *data = NULL;
+ char *svcdir = NULL;
+ char *cachefile = NULL;
+ char *tmp_cachefile = NULL;
+ int tmp_cachefile_fd = 0;
+ int datasize = 0;
+
+ /* Make sure we do not run into locale issues */
+#ifndef __KLIBC__
+ setlocale (LC_ALL, "C");
+#endif
+
+ if (0 != getuid()) {
+ EERROR("Must be root!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ svcdir = get_cnf_entry(RC_CONFD_FILE_NAME, SVCDIR_CONFIG_ENTRY);
+ if (NULL == svcdir) {
+ EERROR("Failed to get config entry '%s'!\n",
+ SVCDIR_CONFIG_ENTRY);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Delete (if needed) volatile directories in svcdir */
+ if (-1 == delete_var_dirs(svcdir)) {
+ /* XXX: Not 100% accurate below message ... */
+ EERROR("Failed to delete '%s', %s", svcdir,
+ "or one of its sub directories!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Create all needed directories in svcdir */
+ if (-1 == create_var_dirs(svcdir)) {
+ EERROR("Failed to create '%s', %s", svcdir,
+ "or one of its sub directories!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ cachefile = strcatpaths(svcdir, LEGACY_CACHE_FILE_NAME);
+ if (NULL == cachefile) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ tmp_cachefile = strcatpaths(cachefile, "XXXXXX");
+ if (NULL == tmp_cachefile) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ exit(EXIT_FAILURE);
+ }
+ /* Replace the "/XXXXXX" with ".XXXXXX"
+ * Yes, I am lazy. */
+ tmp_cachefile[strlen(tmp_cachefile) - strlen(".XXXXXX")] = '.';
+
+ if (-1 == get_rcscripts()) {
+ EERROR("Failed to get rc-scripts list!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (-1 == check_rcscripts_mtime(cachefile)) {
+ EINFO("Caching service dependencies ...\n");
+ DBG_MSG("Regenerating cache file '%s'.\n", cachefile);
+
+ datasize = generate_stage2(&data);
+ if (-1 == datasize) {
+ EERROR("Failed to generate stage2!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (-1 == parse_cache(data, datasize)) {
+ EERROR("Failed to parse stage2 output!\n");
+ free(data);
+ exit(EXIT_FAILURE);
+ }
+
+#if 0
+ tmp_cachefile_fd = open("foo", O_CREAT | O_TRUNC | O_RDWR, 0600);
+ write(tmp_cachefile_fd, data, datasize);
+ close(tmp_cachefile_fd);
+#endif
+
+ free(data);
+
+ if (-1 == service_resolve_dependencies()) {
+ EERROR("Failed to resolve dependencies!\n");
+ exit(EXIT_FAILURE);
+ }
+
+#ifndef __KLIBC__
+ tmp_cachefile_fd = mkstemp(tmp_cachefile);
+#else
+ /* FIXME: Need to add a mkstemp implementation for klibc */
+ tmp_cachefile_fd = open(tmp_cachefile, O_CREAT | O_TRUNC | O_RDWR, 0600);
+#endif
+ if (-1 == tmp_cachefile_fd) {
+ EERROR("Could not open temporary file for writing!\n");
+ exit(EXIT_FAILURE);
+ }
+ cachefile_fd = fdopen(tmp_cachefile_fd, "w");
+ if (NULL == cachefile_fd) {
+ EERROR("Could not open temporary file for writing!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ write_legacy_stage3(cachefile_fd);
+ fclose(cachefile_fd);
+
+ if ((-1 == unlink(cachefile)) && (exists(cachefile))) {
+ EERROR("Could not remove '%s'!\n", cachefile);
+ unlink(tmp_cachefile);
+ exit(EXIT_FAILURE);
+ }
+
+ if (-1 == rename(tmp_cachefile, cachefile)) {
+ EERROR("Could not move temporary file to '%s'!\n",
+ cachefile);
+ unlink(tmp_cachefile);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+#endif
+
diff --git a/src/core/list.h b/src/core/list.h
new file mode 100644
index 0000000..5c478bc
--- /dev/null
+++ b/src/core/list.h
@@ -0,0 +1,446 @@
+/*
+ * Copied from the Linux kernel source tree, version 2.6.0-test1.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ * Ripped out the rcu stuff, as it's not needed.
+ *
+ * $Header$
+ */
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+//#include <linux/stddef.h>
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+//#include <linux/prefetch.h>
+static inline void prefetch(const void *x) {;}
+
+//#include <asm/system.h>
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(struct list_head *head)
+{
+ return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+ struct list_head *head)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+ struct list_head *at = head->next;
+
+ first->prev = head;
+ head->next = first;
+
+ last->next = at;
+ at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+ pos = pos->next, prefetch(pos->next))
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+ pos = pos->prev, prefetch(pos->prev))
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ prefetch(pos->member.next); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member), \
+ prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member), \
+ prefetch(pos->member.prev); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member), \
+ prefetch(pos->member.prev))
+
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop counter.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/*
+ * Double linked lists with a single pointer list head.
+ * Mostly useful for hash tables where the two pointer list head is
+ * too wasteful.
+ * You lose the ability to access the tail in O(1).
+ */
+
+struct hlist_head {
+ struct hlist_node *first;
+};
+
+struct hlist_node {
+ struct hlist_node *next, **pprev;
+};
+
+#define HLIST_HEAD_INIT { .first = NULL }
+#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
+#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
+#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)
+
+static __inline__ int hlist_unhashed(struct hlist_node *h)
+{
+ return !h->pprev;
+}
+
+static __inline__ int hlist_empty(struct hlist_head *h)
+{
+ return !h->first;
+}
+
+static __inline__ void __hlist_del(struct hlist_node *n)
+{
+ struct hlist_node *next = n->next;
+ struct hlist_node **pprev = n->pprev;
+ *pprev = next;
+ if (next)
+ next->pprev = pprev;
+}
+
+static __inline__ void hlist_del(struct hlist_node *n)
+{
+ __hlist_del(n);
+ n->next = LIST_POISON1;
+ n->pprev = LIST_POISON2;
+}
+
+static __inline__ void hlist_del_init(struct hlist_node *n)
+{
+ if (n->pprev) {
+ __hlist_del(n);
+ INIT_HLIST_NODE(n);
+ }
+}
+
+static __inline__ void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+ struct hlist_node *first = h->first;
+ n->next = first;
+ if (first)
+ first->pprev = &n->next;
+ h->first = n;
+ n->pprev = &h->first;
+}
+
+/* next must be != NULL */
+static __inline__ void hlist_add_before(struct hlist_node *n, struct hlist_node *next)
+{
+ n->pprev = next->pprev;
+ n->next = next;
+ next->pprev = &n->next;
+ *(n->pprev) = n;
+}
+
+static __inline__ void hlist_add_after(struct hlist_node *n,
+ struct hlist_node *next)
+{
+ next->next = n->next;
+ *(next->pprev) = n;
+ n->next = next;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+/* Cannot easily do prefetch unfortunately */
+#define hlist_for_each(pos, head) \
+ for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
+ pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+ for (pos = (head)->first; n = pos ? pos->next : 0, pos; \
+ pos = n)
+
+/**
+ * hlist_for_each_entry - iterate over list of given type
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry(tpos, pos, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_continue(tpos, pos, member) \
+ for (pos = (pos)->next; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_from - iterate over a hlist continuing from existing point
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_from(tpos, pos, member) \
+ for (; pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @n: another &struct hlist_node to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ n = pos->next; 1; }) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = n)
+
+#endif
diff --git a/src/core/misc.c b/src/core/misc.c
new file mode 100644
index 0000000..3f1035f
--- /dev/null
+++ b/src/core/misc.c
@@ -0,0 +1,649 @@
+/*
+ * misc.c
+ *
+ * Miscellaneous macro's and functions.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "debug.h"
+#include "misc.h"
+
+char *memrepchr(char **str, char old, char new, size_t size) {
+ char *str_p;
+
+ if ((NULL == str) || (NULL == *str) || (0 == strlen(*str))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ str_p = memchr(*str, old, size);
+
+ while (NULL != str_p) {
+ str_p[0] = new;
+ str_p = memchr(&str_p[1], old, size - (str_p - *str) - 1);
+ }
+
+ return *str;
+}
+
+char *strcatpaths(const char *pathname1, const char *pathname2) {
+ char *new_path = NULL;
+ int lenght;
+
+ if ((NULL == pathname1) || (0 == strlen(pathname1)) ||
+ (NULL == pathname2) || (0 == strlen(pathname2))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Lenght of pathname1 + lenght of pathname2 + '/' if needed */
+ lenght = strlen(pathname1) + strlen(pathname2) + 1;
+ /* lenght + '\0' */
+ new_path = malloc(lenght + 1);
+ if (NULL == new_path) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return NULL;
+ }
+
+ strncpy(new_path, pathname1, lenght);
+ /* Should we add a '/' ? */
+ if (new_path[strlen(new_path)-1] != '/')
+ strncat(new_path, "/", lenght - strlen(new_path));
+ strncat(new_path, pathname2, lenght - strlen(new_path));
+
+ return new_path;
+}
+
+char *strndup(const char *str, size_t size) {
+ char *new_str = NULL;
+ size_t len;
+
+ if (NULL == str) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Check lenght of str without breaching the size limit */
+ for (len = 0;(len < size) && ('\0' != str[len]);len++);
+
+ new_str = malloc(len + 1);
+ if (NULL == new_str) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return NULL;
+ }
+
+ /* Make sure our string is NULL terminated */
+ new_str[len] = '\0';
+
+ return (char *)memcpy(new_str, str, len);
+}
+
+char *gbasename(const char *path) {
+ char *new_path = NULL;
+
+ if ((NULL == path) || (0 == strlen(path))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Copied from glibc */
+ new_path = strrchr (path, '/');
+ return new_path ? new_path + 1 : (char *)path;
+}
+
+
+int exists(const char *pathname) {
+ struct stat buf;
+ int retval;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ retval = lstat(pathname, &buf);
+ if (-1 != retval)
+ return 1;
+
+ /* Clear errno, as we do not want debugging to trigger */
+ errno = 0;
+
+ return 0;
+}
+
+int is_file(const char *pathname, int follow_link) {
+ struct stat buf;
+ int retval;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ retval = follow_link ? stat(pathname, &buf) : lstat(pathname, &buf);
+ if ((-1 != retval) && (S_ISREG(buf.st_mode)))
+ return 1;
+
+ /* Clear errno, as we do not want debugging to trigger */
+ errno = 0;
+
+ return 0;
+}
+
+int is_link(const char *pathname) {
+ struct stat buf;
+ int retval;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ retval = lstat(pathname, &buf);
+ if ((-1 != retval) && (S_ISLNK(buf.st_mode)))
+ return 1;
+
+ /* Clear errno, as we do not want debugging to trigger */
+ errno = 0;
+
+ return 0;
+}
+
+int is_dir(const char *pathname, int follow_link) {
+ struct stat buf;
+ int retval;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ retval = follow_link ? stat(pathname, &buf) : lstat(pathname, &buf);
+ if ((-1 != retval) && (S_ISDIR(buf.st_mode)))
+ return 1;
+
+ /* Clear errno, as we do not want debugging to trigger */
+ errno = 0;
+
+ return 0;
+}
+
+time_t get_mtime(const char *pathname, int follow_link) {
+ struct stat buf;
+ int retval;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ retval = follow_link ? stat(pathname, &buf) : lstat(pathname, &buf);
+ if (-1 != retval)
+ return buf.st_mtime;
+
+ /* Clear errno, as we do not want debugging to trigger */
+ errno = 0;
+
+ return 0;
+}
+
+#ifdef __KLIBC__
+int remove(const char *pathname) {
+ int retval;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (is_dir(pathname, 0))
+ retval = rmdir(pathname);
+ else
+ retval = unlink(pathname);
+
+ return retval;
+}
+#endif
+
+int mktree(const char *pathname, mode_t mode) {
+ char *temp_name = NULL;
+ char *temp_token = NULL;
+ char *token_p;
+ char *token;
+ int retval;
+ int lenght;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Lenght of 'pathname' + extra for "./" if needed */
+ lenght = strlen(pathname) + 2;
+ /* lenght + '\0' */
+ temp_name = malloc(lenght + 1);
+ if (NULL == temp_name) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ return -1;
+ }
+
+ temp_token = strndup(pathname, strlen(pathname));
+ if (NULL == temp_token) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ token_p = temp_token;
+
+ if (pathname[0] == '/')
+ temp_name[0] = '\0';
+ else
+ /* If not an absolute path, make it local */
+ strncpy(temp_name, ".", lenght);
+
+ token = strsep(&token_p, "/");
+ /* First token might be "", but that is OK as it will be when the
+ * pathname starts with '/' */
+ while (NULL != token) {
+ strncat(temp_name, "/", lenght - strlen(temp_name));
+ strncat(temp_name, token, lenght - strlen(temp_name));
+
+ /* If it does not exist, create the dir. If it does exit,
+ * but is not a directory, we will catch it below. */
+ if (!exists(temp_name)) {
+ retval = mkdir(temp_name, mode);
+ if (-1 == retval) {
+ DBG_MSG("Failed to create directory!\n");
+ goto error;
+ }
+ /* Not a directory or symlink pointing to a directory */
+ } else if (!is_dir(temp_name, 1)) {
+ DBG_MSG("Component in pathname is not a directory!\n");
+ errno = ENOTDIR;
+ goto error;
+ }
+
+ do {
+ token = strsep(&token_p, "/");
+ /* The first "" was Ok, but rather skip double '/' after that */
+ } while ((NULL != token) && (0 == strlen(token)));
+ }
+
+ free(temp_name);
+ free(temp_token);
+
+ return 0;
+
+error:
+ free(temp_name);
+ free(temp_token);
+
+ return -1;
+}
+
+int rmtree(const char *pathname) {
+ char **dirlist = NULL;
+ int i = 0;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!exists(pathname)) {
+ DBG_MSG("'%s' does not exists!\n", pathname);
+ errno = ENOENT;
+ return -1;
+ }
+
+ dirlist = ls_dir(pathname, 1);
+ if ((NULL == dirlist) && (0 != errno)) {
+ /* If errno = ENOENT and the directory exists, then it means
+ * it is empty, so we should not error out */
+ if (ENOENT != errno) {
+ DBG_MSG("Could not get listing for '%s'!\n", pathname);
+ return -1;
+ }
+ }
+
+ while ((NULL != dirlist) && (NULL != dirlist[i])) {
+ /* If it is a directory, call rmtree() again with
+ * it as argument */
+ if (is_dir(dirlist[i], 0)) {
+ if (-1 == rmtree(dirlist[i])) {
+ DBG_MSG("Failed to delete sub directory!\n");
+ goto error;
+ }
+ }
+
+ /* Now actually remove it. Note that if it was a directory,
+ * it should already be removed by above rmtree() call */
+ if ((exists(dirlist[i]) && (-1 == remove(dirlist[i])))) {
+ DBG_MSG("Failed to remove '%s'!\n", dirlist[i]);
+ goto error;
+ }
+ i++;
+ }
+
+ STRING_LIST_FREE(dirlist);
+
+ /* Now remove the parent */
+ if (-1 == remove(pathname)) {
+ DBG_MSG("Failed to remove '%s'!\n", pathname);
+ goto error;
+ }
+
+ return 0;
+error:
+ STRING_LIST_FREE(dirlist);
+
+ return -1;
+}
+
+char **ls_dir(const char *pathname, int hidden) {
+ DIR *dirfd;
+ struct dirent *dir_entry;
+ char **dirlist = NULL;
+
+ if ((NULL == pathname) || (0 == strlen(pathname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ dirfd = opendir(pathname);
+ if (NULL == dirfd) {
+ DBG_MSG("Failed to call opendir()!\n");
+ /* errno will be set by opendir() */
+ goto error;
+ }
+
+ do {
+ /* Clear errno to distinguish between EOF and error */
+ errno = 0;
+ dir_entry = readdir(dirfd);
+ /* Only an error if 'errno' != 0, else EOF */
+ if ((NULL == dir_entry) && (0 != errno)) {
+ DBG_MSG("Failed to call readdir()!\n");
+ goto error;
+ }
+ if ((NULL != dir_entry) &&
+ /* Should we display hidden files? */
+ (hidden ? 1 : dir_entry->d_name[0] != '.'))
+ {
+ char *d_name = dir_entry->d_name;
+ char *tmp_p;
+
+ /* Do not list current or parent entries */
+ if ((0 == strcmp(d_name, ".")) ||
+ (0 == strcmp(d_name, "..")))
+ continue;
+
+ tmp_p = strcatpaths(pathname, d_name);
+ if (NULL == tmp_p) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ /* errno = ENOMEM */
+ goto error;
+ }
+
+ STRING_LIST_ADD(dirlist, tmp_p, error);
+ }
+ } while (NULL != dir_entry);
+
+ if ((NULL == dirlist) || (NULL == dirlist[0])) {
+ DBG_MSG("Directory is empty.\n");
+ errno = ENOENT;
+ goto error;
+ }
+
+ closedir(dirfd);
+
+ return dirlist;
+
+error:
+ /* Free dirlist on error */
+ STRING_LIST_FREE(dirlist);
+
+ if (NULL != dirfd) {
+ int old_errno = errno;
+ closedir(dirfd);
+ /* closedir() might have changed it */
+ errno = old_errno;
+ }
+
+ return NULL;
+}
+
+/* This handles simple 'entry="bar"' type variables. If it is more complex
+ * ('entry="$(pwd)"' or such), it will obviously not work, but current behaviour
+ * should be fine for the type of variables we want. */
+char *get_cnf_entry(const char *pathname, const char *entry) {
+ char *buf = NULL;
+ char *tmp_buf = NULL;
+ char *tmp_p;
+ char *value = NULL;
+ char *token;
+ size_t lenght;
+ int count;
+ int current = 0;
+
+
+ if ((NULL == pathname) || (0 == strlen(pathname)) ||
+ (NULL == entry) || (0 == strlen(entry))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* If it is not a file or symlink pointing to a file, bail */
+ if (!is_file(pathname, 1)) {
+ DBG_MSG("Given pathname is not a file or do not exist!\n");
+ /* FIXME: Might need to set this to something better? */
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (-1 == file_map(pathname, &buf, &lenght)) {
+ DBG_MSG("Could not open config file for reading!\n");
+ return NULL;
+ }
+
+ while (current < lenght) {
+ count = buf_get_line(buf, lenght, current);
+
+ tmp_buf = strndup(&buf[current], count);
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ tmp_p = tmp_buf;
+
+ /* Strip leading spaces/tabs */
+ while ((tmp_p[0] == ' ') || (tmp_p[0] == '\t'))
+ tmp_p++;
+
+ /* Get entry and value */
+ token = strsep(&tmp_p, "=");
+ /* Bogus entry or value */
+ if (NULL == token)
+ goto _continue;
+
+ /* Make sure we have a string that is larger than 'entry', and
+ * the first part equals 'entry' */
+ if ((strlen(token) > 0) && (0 == strcmp(entry, token)))
+ {
+ do {
+ /* Bash variables are usually quoted */
+ token = strsep(&tmp_p, "\"\'");
+ /* If quoted, the first match will be "" */
+ } while ((NULL != token) && (0 == strlen(token)));
+
+ /* We have a 'entry='. We respect bash rules, so NULL
+ * value for now (if not already) */
+ if (NULL == token) {
+ /* We might have 'entry=' and later 'entry="bar"',
+ * so just continue for now ... we will handle
+ * it below when 'value == NULL' */
+ if (NULL != value) {
+ free(value);
+ value = NULL;
+ }
+ goto _continue;
+ }
+
+ /* If we have already allocated 'value', free it */
+ if (NULL != value)
+ free(value);
+
+ value = strndup(token, strlen(token));
+ if (NULL == value)
+ /* errno = ENOMEM */
+ goto error;
+
+ /* We do not break, as there might be more than one entry
+ * defined, and as bash uses the last, so should we */
+ /* break; */
+ }
+
+_continue:
+ current += count + 1;
+ free(tmp_buf);
+ /* Set to NULL in case we error out above and have
+ * to free below */
+ tmp_buf = NULL;
+ }
+
+
+ if (NULL == value) {
+ DBG_MSG("Failed to get value for config entry '%s'!\n", entry);
+ errno = ENOMSG;
+ goto error;
+ }
+
+ file_unmap(buf, lenght);
+
+ return value;
+
+error:
+ free(tmp_buf);
+ free(value);
+
+ if (NULL != buf) {
+ int old_errno = errno;
+ file_unmap(buf, lenght);
+ /* unmmap() might have changed it */
+ errno = old_errno;
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Below three functions (file_map, file_unmap and buf_get_line) are
+ * from udev-050 (udev_utils.c).
+ * (Some are slightly modified, please check udev for originals.)
+ *
+ * Copyright (C) 2004 Kay Sievers <kay@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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+int file_map(const char *filename, char **buf, size_t *bufsize)
+{
+ struct stat stats;
+ int fd;
+ int old_errno;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ DBG_MSG("Failed to open file!\n");
+ return -1;
+ }
+
+ if (fstat(fd, &stats) < 0) {
+ DBG_MSG("Failed to stat file!\n");
+ old_errno = errno;
+ close(fd);
+ /* close() might have changed it */
+ errno = old_errno;
+ return -1;
+ }
+
+ *buf = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (*buf == MAP_FAILED) {
+ DBG_MSG("Failed to mmap file!\n");
+ old_errno = errno;
+ close(fd);
+ /* close() might have changed it */
+ errno = old_errno;
+ return -1;
+ }
+ *bufsize = stats.st_size;
+
+ close(fd);
+
+ return 0;
+}
+
+void file_unmap(char *buf, size_t bufsize)
+{
+ munmap(buf, bufsize);
+}
+
+size_t buf_get_line(char *buf, size_t buflen, size_t cur)
+{
+ size_t count = 0;
+
+ for (count = cur; count < buflen && buf[count] != '\n'; count++);
+
+ return count - cur;
+}
+
diff --git a/src/core/misc.h b/src/core/misc.h
new file mode 100644
index 0000000..441a976
--- /dev/null
+++ b/src/core/misc.h
@@ -0,0 +1,288 @@
+/*
+ * misc.h
+ *
+ * Miscellaneous macro's and functions.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#ifndef _MISC_H
+#define _MISC_H
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/* Gentoo style e* printing macro's */
+#define EINFO(_args...) \
+ do { \
+ int old_errno = errno; \
+ printf(" \033[32;01m*\033[0m " _args); \
+ errno = old_errno; \
+ } while (0)
+
+#define EWARN(_args...) \
+ do { \
+ int old_errno = errno; \
+ printf(" \033[33;01m*\033[0m " _args); \
+ errno = old_errno; \
+ } while (0)
+
+#define EERROR(_args...) \
+ do { \
+ int old_errno = errno; \
+ fprintf(stderr, " \033[31;01m*\033[0m " _args); \
+ errno = old_errno; \
+ } while (0)
+
+/* Return true if filename '_name' ends in '_ext' */
+#define CHECK_FILE_EXTENSION(_name, _ext) \
+ (strlen(_name) > strlen(_ext) && \
+ 0 == strncmp(&_name[strlen(_name) - strlen(_ext)], \
+ _ext, strlen(_ext)))
+
+/* For each '_char' in '_string', inc '_count' */
+#define COUNT_CHAR_UP(_string, _char, _count) \
+ do { \
+ int _i; \
+ for (_i = 0;_i < strlen(_string);_i++) \
+ if (_string[_i] == _char) \
+ _count++; \
+ } while (0)
+
+/* For each '_char' in '_string', dec '_count' */
+#define COUNT_CHAR_DN(_string, _char, _count) \
+ do { \
+ int _i; \
+ for (_i = 0;_i < strlen(_string);_i++) \
+ if (_string[_i] == _char) \
+ _count--; \
+ } while (0)
+
+/* Add a new item to a string list. If the pointer to the list is NULL,
+ * allocate enough memory for the amount of entries needed. Ditto for
+ * when it already exists, but we add one more entry than it can
+ * contain. The list is NULL terminated.
+ * NOTE: _only_ memory for the list are allocated, and not for the items - that
+ * should be done by relevant code (unlike STRING_LIST_DEL that will
+ * free the memory) */
+#define STRING_LIST_ADD(_string_list, _item, _error) \
+ do { \
+ char **_tmp_p; \
+ int _i = 0; \
+ if ((NULL == _item) || (0 == strlen(_item))) { \
+ DBG_MSG("Invalid argument passed!\n"); \
+ errno = EINVAL; \
+ goto _error; \
+ } \
+ while ((NULL != _string_list) && (NULL != _string_list[_i])) { \
+ _i++; \
+ } \
+ /* Amount of entries + new + terminator */ \
+ _tmp_p = realloc(_string_list, sizeof(char *) * (_i + 2)); \
+ if (NULL == _tmp_p) { \
+ DBG_MSG("Failed to reallocate list!\n"); \
+ goto _error; \
+ } \
+ _string_list = _tmp_p; \
+ _string_list[_i] = _item; \
+ /* Terminator */ \
+ _string_list[_i+1] = NULL; \
+ } while (0)
+
+/* Add a new item to a string list (foundamental the same as above), but make
+ * sure we have all the items alphabetically sorted. */
+#define STRING_LIST_ADD_SORT(_string_list, _item, _error) \
+ do { \
+ char **_tmp_p; \
+ char *_str_p1; \
+ char *_str_p2; \
+ int _i = 0; \
+ if ((NULL == _item) || (0 == strlen(_item))) { \
+ DBG_MSG("Invalid argument passed!\n"); \
+ errno = EINVAL; \
+ goto _error; \
+ } \
+ while ((NULL != _string_list) && (NULL != _string_list[_i])) \
+ _i++; \
+ /* Amount of entries + new + terminator */ \
+ _tmp_p = realloc(_string_list, sizeof(char *) * (_i + 2)); \
+ if (NULL == _tmp_p) { \
+ DBG_MSG("Failed to reallocate list!\n"); \
+ goto _error; \
+ } \
+ _string_list = _tmp_p; \
+ if (0 == _i) \
+ /* Needed so that the end NULL will propagate
+ * (iow, make sure our 'NULL != _str_p1' test below
+ * do not fail) */ \
+ _string_list[_i] = NULL; \
+ /* Actual terminator that needs adding */ \
+ _string_list[_i+1] = NULL; \
+ _i = 0; \
+ /* See where we should insert the new item to have it all \
+ * alphabetically sorted */ \
+ while (NULL != _string_list[_i]) { \
+ if (strcmp(_string_list[_i], _item) > 0) { \
+ break; \
+ } \
+ _i++; \
+ } \
+ /* Now just insert the new item, and shift the rest one over.
+ * '_str_p2' is temporary storage to swap the indexes in a loop,
+ * and 'str_p1' is used to store the old value across the loop */ \
+ _str_p1 = _string_list[_i]; \
+ _string_list[_i] = _item; \
+ do { \
+ _i++;\
+ _str_p2 = _string_list[_i]; \
+ _string_list[_i] = _str_p1; \
+ _str_p1 = _str_p2; \
+ } while (NULL != _str_p1); \
+ } while (0)
+
+/* Delete one entry from the string list, and shift the rest down if the entry
+ * was not at the end. For now we do not resize the amount of entries the
+ * string list can contain, and free the memory for the matching item */
+#define STRING_LIST_DEL(_string_list, _item, _error) \
+ do { \
+ int _i = 0; \
+ if ((NULL == _item) || (0 == strlen(_item)) || \
+ (NULL == _string_list)) { \
+ DBG_MSG("Invalid argument passed!\n"); \
+ errno = EINVAL; \
+ goto _error; \
+ } \
+ while (NULL != _string_list[_i]) { \
+ if (0 == strcmp(_item, _string_list[_i])) \
+ break; \
+ else \
+ _i++; \
+ } \
+ if (NULL == _string_list[_i]) { \
+ DBG_MSG("Invalid argument passed!\n"); \
+ errno = EINVAL; \
+ goto _error; \
+ } \
+ free(_string_list[_i]); \
+ /* Shift all the following items one forward */ \
+ do { \
+ _string_list[_i] = _string_list[_i+1]; \
+ /* This stupidity is to shutup gcc */ \
+ _i++; \
+ } while (NULL != _string_list[_i]); \
+ } while (0)
+
+/* Step through each entry in the string list, setting '_pos' to the
+ * beginning of the entry. '_counter' is used by the macro as index,
+ * but should not be used by code as index (or if really needed, then
+ * it should usually by +1 from what you expect, and should only be
+ * used in the scope of the macro) */
+#define STRING_LIST_FOR_EACH(_string_list, _pos, _counter) \
+ if ((NULL != _string_list) && (0 == (_counter = 0))) \
+ while (NULL != (_pos = _string_list[_counter++]))
+
+/* Same as above (with the same warning about '_counter'). Now we just
+ * have '_next' that are also used for indexing. Once again rather refrain
+ * from using it if not absolutely needed. The major difference to above,
+ * is that it should be safe from having the item removed from under you. */
+#define STRING_LIST_FOR_EACH_SAFE(_string_list, _pos, _next, _counter) \
+ if ((NULL != _string_list) && (0 == (_counter = 0))) \
+ /* First part of the while checks if this is the
+ * first loop, and if so setup _pos and _next
+ * and increment _counter */ \
+ while ((((0 == _counter) && \
+ (NULL != (_pos = _string_list[_counter])) && \
+ (_pos != (_next = _string_list[++_counter]))) || \
+ /* Second part is when it is not the first loop
+ * and _pos was not removed from under us. We
+ * just increment _counter, and setup _pos and
+ * _next */ \
+ ((0 != _counter) && \
+ (_pos == _string_list[_counter-1]) && \
+ (_next == _string_list[_counter]) && \
+ (NULL != (_pos = _string_list[_counter])) && \
+ (_pos != (_next = _string_list[++_counter]))) || \
+ /* Last part is when _pos was removed from under
+ * us. We basically just setup _pos and _next,
+ * but leave _counter alone */ \
+ ((0 != _counter) && \
+ (_pos != _string_list[_counter-1]) && \
+ (_next == _string_list[_counter-1]) && \
+ (NULL != (_pos = _string_list[_counter-1])) && \
+ (_pos != (_next = _string_list[_counter])))))
+
+/* Just free the whole string list */
+#define STRING_LIST_FREE(_string_list) \
+ do { \
+ if (NULL != _string_list) { \
+ int _i = 0; \
+ while (NULL != _string_list[_i]) \
+ free(_string_list[_i++]); \
+ free(_string_list); \
+ _string_list = NULL; \
+ } \
+ } while (0)
+
+/* String functions. Return a string on success, or NULL on error
+ * or no action taken. On error errno will be set.*/
+char *memrepchr(char **str, char old, char _new, size_t size);
+/* Concat two paths adding '/' if needed. Memory will be allocated
+ * with the malloc() call. */
+char *strcatpaths(const char *pathname1, const char *pathname2);
+
+/* Compat functions for GNU extensions */
+char *strndup(const char *str, size_t size);
+/* Same as basename(3), but do not modify path */
+char *gbasename(const char *path);
+
+/* The following functions do not care about errors - they only return
+ * 1 if 'pathname' exist, and is the type requested, or else 0.
+ * They also might clear errno */
+int exists(const char *pathname);
+int is_file(const char *pathname, int follow_link);
+int is_link(const char *pathname);
+int is_dir(const char *pathname, int follow_link);
+
+/* The following function do not care about errors - it only returns
+ * the mtime of 'pathname' if it exists, and is the type requested,
+ * or else 0. It also might clear errno */
+time_t get_mtime(const char *pathname, int follow_link);
+
+/* The following functions return 0 on success, or -1 with errno set on error. */
+#ifdef __KLIBC__
+int remove(const char *pathname);
+#endif
+int mktree(const char *pathname, mode_t mode);
+int rmtree(const char *pathname);
+
+/* The following return a pointer on success, or NULL with errno set on error.
+ * If it returned NULL, but errno is not set, then there was no error, but
+ * there is nothing to return. */
+char **ls_dir(const char *pathname, int hidden);
+char *get_cnf_entry(const char *pathname, const char *entry);
+
+/* Below three functions (file_map, file_unmap and buf_get_line) are from
+ * udev-050 (udev_utils.c). Please see misc.c for copyright info.
+ * (Some are slightly modified, please check udev for originals.) */
+int file_map(const char *filename, char **buf, size_t *bufsize);
+void file_unmap(char *buf, size_t bufsize);
+size_t buf_get_line(char *buf, size_t buflen, size_t cur);
+
+#endif /* _MISC_H */
+
diff --git a/src/core/parse.c b/src/core/parse.c
new file mode 100644
index 0000000..35926fc
--- /dev/null
+++ b/src/core/parse.c
@@ -0,0 +1,1033 @@
+/*
+ * parse.c
+ *
+ * Parser for Gentoo style rc-scripts.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "debug.h"
+#include "depend.h"
+#include "list.h"
+#include "misc.h"
+#include "parse.h"
+#include "simple-regex.h"
+
+#define READ_PIPE 0
+#define WRITE_PIPE 1
+
+#define PARSE_BUFFER_SIZE 256
+
+#define OUTPUT_MAX_LINE_LENGHT 256
+#define OUTPUT_BUFFER_SIZE (60 * 1024)
+
+/* void PRINT_TO_BUFFER(char **_buf, int _count, label _error, format) */
+#define PRINT_TO_BUFFER(_buf, _count, _error, _output...) \
+ do { \
+ int _i = 0; \
+ /* FIXME: Might do something more dynamic here */ \
+ if (OUTPUT_BUFFER_SIZE < (_count + OUTPUT_MAX_LINE_LENGHT)) { \
+ errno = ENOMEM; \
+ DBG_MSG("Output buffer size too small!\n"); \
+ goto _error; \
+ } \
+ _i = sprintf(&((*_buf)[_count]), _output); \
+ if (0 < _i) \
+ _count += _i + 1; \
+ } while (0)
+
+LIST_HEAD(rcscript_list);
+
+size_t parse_rcscript(char *scriptname, time_t mtime, char **data, size_t index);
+
+size_t parse_print_start(char **data, size_t index);
+size_t parse_print_header(char *scriptname, time_t mtime, char **data, size_t index);
+size_t parse_print_body(char *scriptname, char **data, size_t index);
+size_t parse_print_end(char **data, size_t index);
+
+int get_rcscripts(void) {
+ rcscript_info_t *info;
+ char **file_list = NULL;
+ char *rcscript;
+ char *confd_file = NULL;
+ int count;
+
+ file_list = ls_dir(INITD_DIR_NAME, 0);
+ if (NULL == file_list) {
+ DBG_MSG("'%s' is empty!\n", INITD_DIR_NAME);
+ return -1;
+ }
+
+ STRING_LIST_FOR_EACH(file_list, rcscript, count) {
+ /* Is it a file? */
+ if ((!is_file(rcscript, 1)) ||
+ /* Do not process scripts, source or backup files. */
+ (CHECK_FILE_EXTENSION(rcscript, ".c")) ||
+ (CHECK_FILE_EXTENSION(rcscript, ".bak")) ||
+ (CHECK_FILE_EXTENSION(rcscript, "~")))
+ {
+ DBG_MSG("'%s' is not a valid rc-script!\n",
+ gbasename(rcscript));
+ } else {
+ DBG_MSG("Adding rc-script '%s' to list.\n",
+ gbasename(rcscript));
+
+ info = malloc(sizeof(rcscript_info_t));
+ if (NULL == info) {
+ DBG_MSG("Failed to allocate rcscript_info_t!\n");
+ goto error;
+ }
+
+ /* Copy the name */
+ info->filename = strndup(rcscript, strlen(rcscript));
+ if (NULL == info->filename) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ goto loop_error;
+ }
+
+ /* Get the modification time */
+ info->mtime = get_mtime(rcscript, 1);
+ if (0 == info->mtime) {
+ DBG_MSG("Failed to get modification time for '%s'!\n",
+ rcscript);
+ /* We do not care if it fails - we will pick up
+ * later if there is a problem with the file */
+ }
+
+ /* File name for the conf.d config file (if any) */
+ confd_file = strcatpaths(CONFD_DIR_NAME,
+ gbasename(rcscript));
+ if (NULL == confd_file) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto loop_error;
+ }
+
+ /* Get the modification time of the conf.d file
+ * (if any exists) */
+ info->confd_mtime = get_mtime(confd_file, 1);
+ if (0 == info->confd_mtime) {
+ DBG_MSG("Failed to get modification time for '%s'!\n",
+ confd_file);
+ /* We do not care that it fails, as not all
+ * rc-scripts will have conf.d config files */
+ }
+
+ free(confd_file);
+
+ list_add_tail(&info->node, &rcscript_list);
+
+ continue;
+
+loop_error:
+ if (NULL != info)
+ free(info->filename);
+ free(info);
+
+ goto error;
+ }
+ }
+
+ /* Final check if we have some entries */
+ if (NULL == file_list[0]) {
+ DBG_MSG("No rc-scripts to parse!\n");
+ errno = ENOENT;
+ goto error;
+ }
+
+ STRING_LIST_FREE(file_list);
+
+ return 0;
+
+error:
+ STRING_LIST_FREE(file_list);
+
+ return -1;
+}
+
+/* Returns 0 if we do not need to regen the cache file, else -1 with
+ * errno set if something went wrong */
+int check_rcscripts_mtime(char *cachefile) {
+ rcscript_info_t *info;
+ time_t cache_mtime;
+ time_t rc_conf_mtime;
+ time_t rc_confd_mtime;
+
+ if ((NULL == cachefile) || (0 == strlen(cachefile))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ cache_mtime = get_mtime(cachefile, 1);
+ if (0 == cache_mtime) {
+ DBG_MSG("Could not get modification time for cache file '%s'!\n",
+ cachefile);
+ return -1;
+ }
+
+ /* Get and compare mtime for RC_CONF_FILE_NAME with that of cachefile */
+ rc_conf_mtime = get_mtime(RC_CONF_FILE_NAME, 1);
+ if (rc_conf_mtime > cache_mtime) {
+ DBG_MSG("'%s' have a later modification time than '%s'.\n",
+ RC_CONF_FILE_NAME, cachefile);
+ return -1;
+ }
+ /* Get and compare mtime for RC_CONFD_FILE_NAME with that of cachefile */
+ rc_confd_mtime = get_mtime(RC_CONFD_FILE_NAME, 1);
+ if (rc_confd_mtime > cache_mtime) {
+ DBG_MSG("'%s' have a later modification time than '%s'.\n",
+ RC_CONFD_FILE_NAME, cachefile);
+ return -1;
+ }
+
+ /* Get and compare mtime for each rc-script and its conf.d config file
+ * with that of cachefile */
+ list_for_each_entry(info, &rcscript_list, node) {
+ if ((info->mtime > cache_mtime) ||
+ (info->confd_mtime > cache_mtime)) {
+ DBG_MSG("'%s' have a later modification time than '%s'.\n",
+ info->filename, cachefile);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Return count on success, -1 on error. If it was critical, errno will be set. */
+size_t parse_rcscript(char *scriptname, time_t mtime, char **data, size_t index) {
+ regex_data_t tmp_data;
+ char *buf = NULL;
+ char *tmp_buf = NULL;
+ size_t write_count = index;
+ size_t lenght;
+ int count;
+ int current = 0;
+
+ if ((NULL == scriptname) || (0 == strlen(scriptname))) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (-1 == file_map(scriptname, &buf, &lenght)) {
+ DBG_MSG("Could not open '%s' for reading!\n",
+ gbasename(scriptname));
+ return -1;
+ }
+
+ while (current < lenght) {
+ count = buf_get_line(buf, lenght, current);
+
+ tmp_buf = strndup(&buf[current], count);
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+
+ if (0 == current) {
+ /* Check if it starts with '#!/sbin/runscript' */
+ DO_REGEX(tmp_data, tmp_buf,
+ "[ \t]*#![ \t]*/sbin/runscript[ \t]*.*", error);
+ if (REGEX_FULL_MATCH != tmp_data.match) {
+ DBG_MSG("'%s' is not a valid rc-script!\n",
+ gbasename(scriptname));
+ errno = 0;
+ goto error;
+ }
+
+ /* We do not want rc-scripts ending in '.sh' */
+ if (CHECK_FILE_EXTENSION(scriptname, ".sh")) {
+ EWARN("'%s' is invalid (should not end with '.sh')!\n",
+ gbasename(scriptname));
+ errno = 0;
+ goto error;
+ }
+ DBG_MSG("Parsing '%s'.\n", gbasename(scriptname));
+
+ write_count = parse_print_header(gbasename(scriptname),
+ mtime, data, write_count);
+ if (-1 == write_count) {
+ DBG_MSG("Failed to call parse_print_header()!\n");
+ goto error;
+ }
+
+ goto _continue;
+ }
+
+ /* Check for lines with comments, and skip them */
+ DO_REGEX(tmp_data, tmp_buf, "^[ \t]*#", error);
+ if (REGEX_MATCH(tmp_data))
+ goto _continue;
+
+ /* If the line contains 'depend()', set 'got_depend' */
+ DO_REGEX(tmp_data, tmp_buf, "depend[ \t]*()[ \t]*{?", error);
+ if (REGEX_MATCH(tmp_data)) {
+ DBG_MSG("Got 'depend()' function.\n");
+
+ write_count = parse_print_body(gbasename(scriptname),
+ data, write_count);
+ if (-1 == write_count) {
+ DBG_MSG("Failed to call parse_print_body()!\n");
+ goto error;
+ }
+
+ /* Need to have the 'source', as parse_cache() checks for
+ * second arg */
+ PRINT_TO_BUFFER(data, write_count, error,
+ " . \"%s\" >/dev/null 2>&1 || echo \"FAILED source\"\n",
+ scriptname);
+
+ write_count = parse_print_end(data, write_count);
+ if (-1 == write_count) {
+ DBG_MSG("Failed to call parse_print_end()!\n");
+ goto error;
+ }
+
+ /* Make sure this is the last loop */
+ current += lenght;
+ goto _continue;
+ }
+
+_continue:
+ current += count + 1;
+ free(tmp_buf);
+ }
+
+ file_unmap(buf, lenght);
+
+ return write_count;
+
+error:
+ free(tmp_buf);
+ if (NULL != buf) {
+ int old_errno = errno;
+ file_unmap(buf, lenght);
+ /* file_unmap() might have changed it */
+ errno = old_errno;
+ }
+
+ return -1;
+}
+
+
+size_t generate_stage1(char **data) {
+ rcscript_info_t *info;
+ size_t write_count = 0;
+ size_t tmp_count;
+
+ write_count = parse_print_start(data, write_count);
+ if (-1 == write_count) {
+ DBG_MSG("Failed to call parse_print_start()!\n");
+ return -1;
+ }
+
+ list_for_each_entry(info, &rcscript_list, node) {
+ tmp_count = parse_rcscript(info->filename, info->mtime, data, write_count);
+ if (-1 == tmp_count) {
+ DBG_MSG("Failed to parse '%s'!\n",
+ gbasename(info->filename));
+
+ /* If 'errno' is set, it is critical (hopefully) */
+ if (0 != errno)
+ return -1;
+ } else {
+ write_count = tmp_count;
+ }
+ }
+
+ return write_count;
+}
+
+/* Empty signal handler for SIGPIPE */
+static void sig_handler(int signum) {
+ return;
+}
+
+/* Returns data's lenght on success, else -1 on error. */
+size_t generate_stage2(char **data) {
+ /* parent_pfds is used to send data to the parent
+ * (thus the parent only use the read pipe, and the
+ * child uses the write pipe)
+ */
+ int parent_pfds[2];
+ /* child_pfds is used to send data to the child
+ * (thus the child only use the read pipe, and the
+ * parent uses the write pipe)
+ */
+ int child_pfds[2];
+ pid_t child_pid;
+ size_t write_count = 0;
+ int old_errno = 0;
+
+ /* Pipe to send data to parent */
+ if (-1 == pipe(parent_pfds)) {
+ DBG_MSG("Failed to open 'parent_pfds' pipe!\n");
+ goto error;
+ }
+ /* Pipe to send data to childd */
+ if (-1 == pipe(child_pfds)) {
+ DBG_MSG("Failed to open 'child_pfds' pipe!\n");
+ /* Close parent_pfds */
+ goto error_c_parent;
+ }
+
+ /* Zero data */
+ *data = NULL;
+
+ child_pid = fork();
+ if (-1 == child_pid) {
+ DBG_MSG("Failed to fork()!\n");
+ /* Close all pipes */
+ goto error_c_all;
+ }
+ if (0 == child_pid) {
+ /***
+ *** In child
+ ***/
+
+ char *const argv[] = {
+ "bash",
+ "--noprofile",
+ "--norc",
+ "--",
+ NULL
+ };
+
+ /* Close the sides of the pipes we do not use */
+ close(child_pfds[WRITE_PIPE]); /* Only used for reading */
+ close(parent_pfds[READ_PIPE]); /* Only used for writing */
+
+ /* dup2 child side read pipe to STDIN */
+ dup2(child_pfds[READ_PIPE], STDIN_FILENO);
+ /* dup2 child side write pipe to STDOUT */
+ dup2(parent_pfds[WRITE_PIPE], STDOUT_FILENO);
+
+ /* We need to be in INITD_DIR_NAME for 'before'/'after' '*' to work */
+ if (-1 == chdir(INITD_DIR_NAME)) {
+ DBG_MSG("Failed to chdir to '%s'!\n", INITD_DIR_NAME);
+ exit(1);
+ }
+
+ if (-1 == execv(SHELL_PARSER, argv)) {
+ DBG_MSG("Failed to execv %s!\n", SHELL_PARSER);
+ exit(1);
+ }
+ } else {
+ /***
+ *** In parent
+ ***/
+
+ struct sigaction act_new;
+ struct sigaction act_old;
+ struct timeval tv;
+#if defined(USE_WRITE_SELECT)
+ fd_set write_fds;
+#endif
+ fd_set read_fds;
+ char buf[PARSE_BUFFER_SIZE+1];
+ char *stage1_data = NULL;
+ size_t stage1_write_count = 0;
+ size_t stage1_written = 0;
+#if defined(USE_WRITE_SELECT)
+ int max_write_fds = child_pfds[WRITE_PIPE] + 1;
+#endif
+ int max_read_fds = parent_pfds[READ_PIPE] + 1;
+ int status = 0;
+ int read_count;
+ int closed_write_pipe = 0;
+ int tmp_pid = 0;
+
+ DBG_MSG("Child pid = %i\n", child_pid);
+
+ /* Set signal handler for SIGPIPE to empty in case bash errors
+ * out. It will then close the write pipe, and instead of us
+ * getting SIGPIPE, we can handle the write error like normal.
+ */
+ memset(&act_new, 0x00, sizeof(act_new));
+ act_new.sa_handler = (void (*) (int))sig_handler;
+ sigemptyset (&act_new.sa_mask);
+ act_new.sa_flags = 0;
+ sigaction(SIGPIPE, &act_new, &act_old);
+
+ /* Close the sides of the pipes we do not use */
+ close(parent_pfds[WRITE_PIPE]); /* Only used for reading */
+ close(child_pfds[READ_PIPE]); /* Only used for writing */
+
+ stage1_data = malloc(OUTPUT_BUFFER_SIZE + 1);
+ if (NULL == stage1_data) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ goto error_c_p_side;
+ }
+
+ /* Pipe parse_rcscripts() to bash */
+ stage1_write_count = generate_stage1(&stage1_data);
+ if (-1 == stage1_write_count) {
+ DBG_MSG("Failed to generate stage1!\n");
+ goto error_c_p_side;
+ }
+
+#if 0
+ int tmp_fd = open("bar", O_CREAT | O_TRUNC | O_RDWR, 0600);
+ write(tmp_fd, stage1_data, stage1_write_count);
+ close(tmp_fd);
+#endif
+
+ /* Do setup for select() */
+ tv.tv_sec = 0; /* We do not want to wait for select() */
+ tv.tv_usec = 0; /* Same thing here */
+#if defined(USE_WRITE_SELECT)
+ FD_ZERO(&write_fds);
+ FD_SET(child_pfds[WRITE_PIPE], &write_fds);
+#endif
+ FD_ZERO(&read_fds);
+ FD_SET(parent_pfds[READ_PIPE], &read_fds);
+
+ do {
+#if defined(USE_WRITE_SELECT)
+ fd_set wwrite_fds = write_fds;
+#endif
+ fd_set wread_fds = read_fds;
+ int tmp_count = 0;
+#if defined(USE_WRITE_SELECT)
+ int do_write = 0;
+#endif
+ int do_read = 0;
+
+ /* Check if we can read from parent_pfds[READ_PIPE] */
+ select(max_read_fds, &wread_fds, NULL, NULL, &tv);
+ do_read = FD_ISSET(parent_pfds[READ_PIPE], &wread_fds);
+
+ /* While there is data to be written */
+ if (stage1_written < stage1_write_count) {
+#if defined(USE_WRITE_SELECT)
+ /* Check if we can write */
+ select(max_write_fds, NULL, &wwrite_fds,
+ NULL, &tv);
+ do_write = FD_ISSET(child_pfds[WRITE_PIPE],
+ &wwrite_fds);
+
+ /* If we can write, or there is nothing to
+ * read, keep feeding the write pipe */
+ if (do_write || !do_read) {
+#else
+ if (!do_read) {
+#endif
+ tmp_count = write(child_pfds[WRITE_PIPE],
+ &stage1_data[stage1_written],
+ strlen(&stage1_data[stage1_written]));
+ if (-1 == tmp_count) {
+ DBG_MSG("Error writing to child_pfds[WRITE_PIPE]!\n");
+ goto failed;
+ }
+ /* What was written before, plus what
+ * we wrote now as well as the ending
+ * '\0' of the line */
+ stage1_written += tmp_count + 1;
+
+ /* Close the write pipe if we done
+ * writing to get a EOF signaled to
+ * bash */
+ if (stage1_written >= stage1_write_count) {
+ closed_write_pipe = 1;
+ close(child_pfds[WRITE_PIPE]);
+ }
+ }
+ }
+
+ if (do_read) {
+ read_count = read(parent_pfds[READ_PIPE], buf,
+ PARSE_BUFFER_SIZE);
+ if (-1 == read_count) {
+ DBG_MSG("Error reading parent_pfds[READ_PIPE]!\n");
+ goto failed;
+ }
+ if (read_count > 0) {
+ char *tmp_p;
+
+ tmp_p = realloc(*data, write_count +
+ read_count);
+ if (NULL == tmp_p) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ goto failed;
+ }
+
+ memcpy(&tmp_p[write_count], buf,
+ read_count);
+
+ *data = tmp_p;
+ write_count += read_count;
+ }
+ }
+ tmp_pid = waitpid(child_pid, &status, WNOHANG);
+ } while (0 == tmp_pid);
+
+failed:
+ /* Set old_errno to disable child exit code checking below */
+ if (0 != errno)
+ old_errno = errno;
+
+ free(stage1_data);
+
+ if (0 == closed_write_pipe)
+ close(child_pfds[WRITE_PIPE]);
+ close(parent_pfds[READ_PIPE]);
+
+ /* Restore the old signal handler for SIGPIPE */
+ sigaction(SIGPIPE, &act_old, NULL);
+
+ if (tmp_pid != child_pid)
+ /* Wait for bash to finish */
+ waitpid(child_pid, &status, 0);
+ /* If old_errno is set, we had an error in the read loop, so do
+ * not worry about the child's exit code */
+ if (0 == old_errno) {
+ if ((!WIFEXITED(status)) || (0 != WEXITSTATUS(status))) {
+ DBG_MSG("Bash failed with status 0x%x!\n", status);
+ goto error;
+ }
+ } else {
+ /* Right, we had an error, so set errno, and exit */
+ errno = old_errno;
+ goto error;
+ }
+ }
+
+ return write_count;
+
+ /* Close parent side pipes */
+error_c_p_side:
+ old_errno = errno;
+ close(child_pfds[WRITE_PIPE]);
+ close(parent_pfds[READ_PIPE]);
+ /* close() might have changed it */
+ errno = old_errno;
+ goto error;
+
+ /* Close all pipes */
+error_c_all:
+ old_errno = errno;
+ close(child_pfds[READ_PIPE]);
+ close(child_pfds[WRITE_PIPE]);
+ /* close() might have changed it */
+ errno = old_errno;
+
+ /* Only close parent's pipes */
+error_c_parent:
+ old_errno = errno;
+ close(parent_pfds[READ_PIPE]);
+ close(parent_pfds[WRITE_PIPE]);
+ /* close() might have changed it */
+ errno = old_errno;
+
+error:
+ return -1;
+}
+
+int write_legacy_stage3(FILE *output) {
+ service_info_t *info;
+ char *service;
+ int count;
+ int index = 0;
+ int dep_count;
+ int i;
+
+ if (-1 == fileno(output)) {
+ DBG_MSG("Bad output stream!\n");
+ return -1;
+ }
+
+ fprintf(output, "rc_type_ineed=2\n");
+ fprintf(output, "rc_type_needsme=3\n");
+ fprintf(output, "rc_type_iuse=4\n");
+ fprintf(output, "rc_type_usesme=5\n");
+ fprintf(output, "rc_type_ibefore=6\n");
+ fprintf(output, "rc_type_iafter=7\n");
+ fprintf(output, "rc_type_broken=8\n");
+ fprintf(output, "rc_type_mtime=9\n");
+ fprintf(output, "rc_index_scale=10\n\n");
+ fprintf(output, "declare -a RC_DEPEND_TREE\n\n");
+
+ list_for_each_entry(info, &service_info_list, node) {
+ index++;
+ }
+ if (0 == index) {
+ EERROR("No services to generate dependency tree for!\n");
+ return -1;
+ }
+
+ fprintf(output, "RC_DEPEND_TREE[0]=%i\n\n", index);
+
+ index = 1;
+
+ list_for_each_entry(info, &service_info_list, node) {
+#if 0
+ /* Make it easier to compare old depscan.sh output and this
+ * output as it puts 'net' right in the middle */
+ if (0 == strcmp("net", info->name))
+ continue;
+#endif
+ fprintf(output, "RC_DEPEND_TREE[%i]=\"%s\"\n", index*11, info->name);
+
+ for (i = 0;i <= BROKEN;i++) {
+ dep_count = 0;
+
+ fprintf(output, "RC_DEPEND_TREE[%i+%i]=", (index * 11), (i + 2));
+
+ STRING_LIST_FOR_EACH(info->depend_info[i], service, count) {
+ if (0 == dep_count)
+ fprintf(output, "\"%s", service);
+ else
+ fprintf(output, " %s", service);
+
+ dep_count++;
+ }
+
+ if (dep_count > 0)
+ fprintf(output, "\"\n");
+ else
+ fprintf(output, "\n");
+ }
+
+ fprintf(output, "RC_DEPEND_TREE[%i+9]=", index*11);
+ fprintf(output, "\n");
+
+ fprintf(output, "RC_DEPEND_TREE[%i+10]=\"%li\"\n\n", index*11,
+ info->mtime);
+ index++;
+ }
+
+ fprintf(output, "RC_GOT_DEPTREE_INFO=\"yes\"\n");
+
+ info = service_get_virtual("logger");
+ if (NULL == info) {
+ DBG_MSG("No service provides the 'logger' logger virtual!\n");
+ fprintf(output, "\nLOGGER_SERVICE=\n");
+ } else {
+ fprintf(output, "\nLOGGER_SERVICE=\"%s\"\n", info->name);
+ }
+
+
+ return 0;
+}
+
+int parse_cache(const char *data, size_t lenght) {
+ service_info_t *info;
+ service_type_t type = ALL_SERVICE_TYPE_T;
+ char *tmp_buf = NULL;
+ char *rc_name = NULL;
+ char *tmp_p;
+ char *token;
+ char *field;
+ int count;
+ int current = 0;
+ int retval;
+
+ if ((NULL == data) || (lenght <= 0)) {
+ DBG_MSG("Invalid argument passed!\n");
+ errno = EINVAL;
+ goto error;
+ }
+
+ while (current < lenght) {
+ count = buf_get_line((char *)data, lenght, current);
+
+ tmp_buf = strndup(&data[current], count);
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ tmp_p = tmp_buf;
+
+ /* Strip leading spaces/tabs */
+ while ((tmp_p[0] == ' ') || (tmp_p[0] == '\t'))
+ tmp_p++;
+
+ /* Get FIELD name and FIELD value */
+ token = strsep(&tmp_p, " ");
+
+ /* FIELD name empty/bogus? */
+ if ((NULL == token) || (0 == strlen(token)) ||
+ /* We got an empty FIELD value */
+ (NULL == tmp_p) || (0 == strlen(tmp_p))) {
+ DBG_MSG("Parsing stopped due to short read!\n");
+ errno = EMSGSIZE;
+ goto error;
+ }
+
+ if (0 == strcmp(token, FIELD_RCSCRIPT)) {
+ DBG_MSG("Field = '%s', value = '%s'\n", token, tmp_p);
+
+ /* Add the service to the list, and initialize all data */
+ retval = service_add(tmp_p);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add %s to service list!\n",
+ tmp_p);
+ goto error;
+ }
+
+ info = service_get_info(tmp_p);
+ if (NULL == info) {
+ DBG_MSG("Failed to get info for '%s'!\n", tmp_p);
+ goto error;
+ }
+ /* Save the rc-script name for next passes of loop */
+ rc_name = info->name;
+
+ goto _continue;
+ }
+
+ if (NULL == rc_name) {
+ DBG_MSG("Other fields should come after '%s'!\n", FIELD_RCSCRIPT);
+ goto error;
+ }
+
+ if (0 == strcmp(token, FIELD_FAILED)) {
+ EWARN("'%s' has syntax errors, please correct!\n", rc_name);
+ /* FIXME: Need to think about what to do syntax BROKEN
+ * services */
+ retval = service_add_dependency(rc_name, rc_name, BROKEN);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency '%s' to service '%s', type '%s'!\n",
+ token, rc_name, field);
+ goto error;
+ }
+ goto _continue;
+ }
+
+ if (0 == strcmp(token, FIELD_NEED)) {
+ type = NEED;
+ goto have_dep_field;
+ }
+
+ if (0 == strcmp(token, FIELD_USE)) {
+ type = USE;
+ goto have_dep_field;
+ }
+
+ if (0 == strcmp(token, FIELD_BEFORE)) {
+ type = BEFORE;
+ goto have_dep_field;
+ }
+
+ if (0 == strcmp(token, FIELD_AFTER)) {
+ type = AFTER;
+ goto have_dep_field;
+ }
+
+ if (0 == strcmp(token, FIELD_PROVIDE)) {
+ type = PROVIDE;
+ goto have_dep_field;
+ }
+
+ if (type < ALL_SERVICE_TYPE_T) {
+have_dep_field:
+ /* Get the first value *
+ * As the values are passed to a bash function, and we
+ * then use 'echo $*' to parse them, they should only
+ * have one space between each value ... */
+ token = strsep(&tmp_p, " ");
+
+ /* Get the correct type name */
+ field = service_type_names[type];
+
+ while (NULL != token) {
+ DBG_MSG("Field = '%s', service = '%s', value = '%s'\n",
+ field, rc_name, token);
+
+ retval = service_add_dependency(rc_name, token, type);
+ if (-1 == retval) {
+ DBG_MSG("Failed to add dependency '%s' to service '%s', type '%s'!\n",
+ token, rc_name, field);
+ goto error;
+ }
+
+ /* Get the next value (if any) */
+ token = strsep(&tmp_p, " ");
+ }
+
+ goto _continue;
+ }
+
+ if (0 == strcmp(token, FIELD_MTIME)) {
+ time_t mtime = 0;
+
+ /* Just use the first value, and ignore the rest */
+ token = strsep(&tmp_p, " ");
+
+ if (NULL != token)
+ mtime = atoi(token);
+
+ retval = service_set_mtime(rc_name, mtime);
+ if (-1 == retval) {
+ DBG_MSG("Failed to set mtime for service '%s'!\n",
+ rc_name);
+ goto error;
+ }
+
+ /* Some debugging in case we have some corruption or
+ * other issues */
+ token = strsep(&tmp_p, " ");
+ if (NULL != token)
+ DBG_MSG("Too many falues for field '%s'!\n",
+ FIELD_MTIME);
+
+ goto _continue;
+ }
+
+ /* Fall through */
+ DBG_MSG("Unknown FIELD in data!\n");
+
+_continue:
+ type = ALL_SERVICE_TYPE_T;
+ current += count + 1;
+ free(tmp_buf);
+ /* Do not free 'rc_name', as it should be consistant
+ * across loops */
+ }
+
+ return 0;
+
+error:
+ free(tmp_buf);
+
+ return -1;
+}
+
+size_t parse_print_start(char **data, size_t index) {
+ size_t write_count = index;
+
+ PRINT_TO_BUFFER(data, write_count, error, ". /sbin/functions.sh\n");
+ PRINT_TO_BUFFER(data, write_count, error, "[ -e /etc/rc.conf ] && . /etc/rc.conf\n\n");
+// PRINT_TO_BUFFER(data, write_count, error, "set -e\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, "need() {\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -n \"$*\" ] && echo \"NEED $*\"; return 0\n");
+ PRINT_TO_BUFFER(data, write_count, error, "}\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, "use() {\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -n \"$*\" ] && echo \"USE $*\"; return 0\n");
+ PRINT_TO_BUFFER(data, write_count, error, "}\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, "before() {\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -n \"$*\" ] && echo \"BEFORE $*\"; return 0\n");
+ PRINT_TO_BUFFER(data, write_count, error, "}\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, "after() {\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -n \"$*\" ] && echo \"AFTER $*\"; return 0\n");
+ PRINT_TO_BUFFER(data, write_count, error, "}\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, "provide() {\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -n \"$*\" ] && echo \"PROVIDE $*\"; return 0\n");
+ PRINT_TO_BUFFER(data, write_count, error, "}\n\n");
+
+ return write_count;
+
+error:
+ return -1;
+}
+
+size_t parse_print_header(char *scriptname, time_t mtime, char **data, size_t index) {
+ size_t write_count = index;
+
+ PRINT_TO_BUFFER(data, write_count, error, "#*** %s ***\n\n", scriptname);
+ PRINT_TO_BUFFER(data, write_count, error, "myservice=\"%s\"\n", scriptname);
+ PRINT_TO_BUFFER(data, write_count, error, "echo \"RCSCRIPT ${myservice}\"\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, "echo \"MTIME %li\"\n\n", mtime);
+
+ return write_count;
+
+error:
+ return -1;
+}
+
+size_t parse_print_body(char *scriptname, char **data, size_t index) {
+ size_t write_count = index;
+ char *tmp_buf = NULL;
+ char *tmp_ptr;
+ char *base;
+ char *ext;
+
+ tmp_buf = strndup(scriptname, strlen(scriptname));
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+
+ /*
+ * Rather do the next block in C than bash, in case we want to
+ * use ash or another shell in the place of bash
+ */
+
+ /* bash: base="${myservice%%.*}" */
+ base = tmp_buf;
+ tmp_ptr = strchr(tmp_buf, '.');
+ if (NULL != tmp_ptr) {
+ tmp_ptr[0] = '\0';
+ tmp_ptr++;
+ } else {
+ tmp_ptr = tmp_buf;
+ }
+ /* bash: ext="${myservice##*.}" */
+ ext = strrchr(tmp_ptr, '.');
+ if (NULL == ext)
+ ext = tmp_ptr;
+
+ PRINT_TO_BUFFER(data, write_count, error, "\n");
+ PRINT_TO_BUFFER(data, write_count, error, " # Get settings for rc-script ...\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -e \"/etc/conf.d/${myservice}\" ] && \\\n");
+ PRINT_TO_BUFFER(data, write_count, error, " . \"/etc/conf.d/${myservice}\"\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ -e /etc/conf.d/net ] && \\\n");
+ PRINT_TO_BUFFER(data, write_count, error, " [ \"%s\" = \"net\" ] && \\\n", base);
+ PRINT_TO_BUFFER(data, write_count, error, " [ \"%s\" != \"${myservice}\" ] && \\\n", ext);
+ PRINT_TO_BUFFER(data, write_count, error, " . /etc/conf.d/net\n");
+ PRINT_TO_BUFFER(data, write_count, error, " depend() {\n");
+ PRINT_TO_BUFFER(data, write_count, error, " return 0\n");
+ PRINT_TO_BUFFER(data, write_count, error, " }\n\n");
+ PRINT_TO_BUFFER(data, write_count, error, " # Actual depend() function ...\n");
+
+ free(tmp_buf);
+
+ return write_count;
+
+error:
+ return -1;
+}
+
+size_t parse_print_end(char **data, size_t index) {
+ size_t write_count = index;
+
+ PRINT_TO_BUFFER(data, write_count, error, "\n");
+ PRINT_TO_BUFFER(data, write_count, error, " depend\n");
+ PRINT_TO_BUFFER(data, write_count, error, "\n\n");
+
+ return write_count;
+
+error:
+ return -1;
+}
+
diff --git a/src/core/parse.h b/src/core/parse.h
new file mode 100644
index 0000000..895fc6b
--- /dev/null
+++ b/src/core/parse.h
@@ -0,0 +1,110 @@
+/*
+ * parse.h
+ *
+ * Parser for Gentoo style rc-scripts.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#ifndef _PARSE_H
+#define _PARSE_H
+
+#include <sys/types.h>
+#include "list.h"
+
+#define RC_CONF_FILE_NAME "/etc/rc.conf"
+#define RC_CONFD_FILE_NAME "/etc/conf.d/rc"
+#define INITD_DIR_NAME "/etc/init.d/"
+#define CONFD_DIR_NAME "/etc/conf.d/"
+
+#define SVCDIR_CONFIG_ENTRY "svcdir"
+
+#define SHELL_PARSER "/bin/bash"
+
+#define LEGACY_CACHE_FILE_NAME "deptree"
+
+#define FIELD_RCSCRIPT "RCSCRIPT"
+#define FIELD_NEED "NEED"
+#define FIELD_USE "USE"
+#define FIELD_BEFORE "BEFORE"
+#define FIELD_AFTER "AFTER"
+#define FIELD_PROVIDE "PROVIDE"
+#define FIELD_MTIME "MTIME"
+#define FIELD_FAILED "FAILED"
+
+typedef struct {
+ struct list_head node;
+
+ char *filename;
+ time_t mtime;
+ time_t confd_mtime;
+} rcscript_info_t;
+
+struct list_head rcscript_list;
+
+int get_rcscripts(void);
+int check_rcscripts_mtime(char *cachefile);
+size_t generate_stage1(char **data);
+size_t generate_stage2(char **data);
+size_t read_stage2(char **data);
+int write_stage2(FILE *outfile);
+size_t generate_stage3(char **data);
+size_t read_stage3(char **data);
+int write_stage3(FILE *outfile);
+int write_legacy_stage3(FILE *output);
+int parse_cache(const char *data, size_t lenght);
+
+/*
+ * get_rcscripts()
+ * |
+ * V
+ * check_rcscripts_mtime() ------------------------------> read_stage3()
+ * | |
+ * | |
+ * V V
+ * generate_stage1() (Called by generate_stage2()) parse_cache()
+ * | |
+ * | |
+ * V |
+ * generate_stage2() ----> write_stage2() (Debugging) |
+ * | |
+ * | |
+ * | === parse_cache() |
+ * V | | |
+ * generate_stage3() ==| | |
+ * | | | |
+ * | | V |
+ * | === service_resolve_dependencies() |
+ * | |
+ * | |
+ * |-------> write_legacy_stage3() (Proof of Concept |
+ * | or Debugging) |
+ * | |
+ * V |
+ * write_stage3() |
+ * | |
+ * | V
+ * |<-------------------------------------------------------
+ * |
+ * V
+ *
+ */
+
+#endif /* _PARSE_H */
+
diff --git a/src/core/simple-regex.c b/src/core/simple-regex.c
new file mode 100644
index 0000000..385f1f4
--- /dev/null
+++ b/src/core/simple-regex.c
@@ -0,0 +1,854 @@
+/*
+ * simple_regex.c
+ *
+ * Simle regex library.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+/*
+ * Some notes:
+ *
+ * - This is a very simple regex library (read: return a match if some string
+ * matches some regex). It is probably not POSIX (if there are a POSIX or
+ * other standard) compatible.
+ *
+ * - I primarily wrote it to _not_ use glibc type regex functions, in case we
+ * might want to use it in code that have to be linked agaist klibc, etc.
+ *
+ * - It really is not optimized in any way yet.
+ *
+ * - Supported operators are:
+ *
+ * '.', '?', '*', '+' - So called 'wildcards'
+ * '[a-z]', '[^a-z]' - Basic 'lists'. Note that 'a-z' just specify that
+ * it supports basic lists as well as sequences ..
+ * The '^' is for an inverted list of course.
+ * '^', '$' - The 'from start' and 'to end' operators. If these
+ * are not used at the start ('^') or end ('$') of the
+ * regex, they will be treated as normal characters
+ * (this of course exclude the use of '^' in a 'list').
+ *
+ * - If an invalid argument was passed, the functions returns 0 with
+ * 'regex_data-match == 0' (no error with no match) rather than -1. It may
+ * not be consistant with other practices, but I personally do not feel it is
+ * a critical error for these types of functions, and there are debugging you
+ * can enable to verify that there are no such issues.
+ *
+ * - __somefunction() is usually a helper function for somefunction(). I guess
+ * recursion might be an alternative, but I try to avoid it.
+ *
+ * - In general if we are matching a 'wildcard' ('*', '+' or '?'), a 'word'
+ * (read: some part of the regex that do not contain a 'wildcard' or 'list')
+ * will have a greater 'weight' than the 'wildcard'. This means that we
+ * will only continue to evaluate the 'wildcard' until the following 'word'
+ * (if any) matches. Currently this do not hold true for a 'list' not
+ * followed by a 'wildcard' - I might fix this in future.
+ *
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "misc.h"
+#include "simple-regex.h"
+
+/* Macro to check if a regex_data_t pointer is valid */
+#define CHECK_REGEX_DATA_P(_regex_data, _on_error) \
+ do { \
+ if ((NULL == _regex_data) || \
+ (NULL == _regex_data->data) || \
+ /* We do not check for this, as it might still \
+ * provide a match ('*' or '?' wildcard) */ \
+ /* (0 == strlen(_regex_data->data)) || */ \
+ (NULL == _regex_data->regex) || \
+ (0 == strlen(_regex_data->regex))) {\
+ DBG_MSG("Invalid argument passed!\n"); \
+ goto _on_error; \
+ } \
+ } while (0)
+
+size_t get_word(const char *regex, char **r_word);
+int match_word(regex_data_t *regex_data);
+size_t get_list_size(const char *regex);
+size_t get_list(const char *regex, char **r_list);
+int __match_list(regex_data_t *regex_data);
+int match_list(regex_data_t *regex_data);
+size_t get_wildcard(const char *regex, char *r_wildcard);
+int __match_wildcard(regex_data_t *regex_data,
+int (*match_func)(regex_data_t *regex_data), const char *regex);
+int match_wildcard(regex_data_t *regex_data);
+int __match(regex_data_t *regex_data);
+
+/*
+ * Return values for match_* functions
+ *
+ * 0 - There was no error. If there was a match, regex_data->match
+ * - will be > 0 (this is the definitive check - if not true, the
+ * - other values of the struct may be bogus), regex_data->count
+ * - will be the amount of data that was matched (might be 0 for
+ * - some wildcards), and regex_data->r_count will be > 0.
+ *
+ * -1 - An error occured. Check errno for more info.
+ *
+ */
+
+size_t get_word(const char *regex, char **r_word) {
+ char *r_list;
+ char *tmp_p;
+ size_t count = 0;
+ size_t tmp_count;
+
+ /* NULL string means we do not have a word */
+ if ((NULL == regex) || (0 == strlen(regex))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ *r_word = malloc(strlen(regex) + 1);
+ if (NULL == r_word) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return 0;
+ }
+ tmp_p = *r_word;
+
+ while (strlen(regex) > 0) {
+ switch (regex[0]) {
+ case '*':
+ case '+':
+ case '?':
+ /* If its a wildcard, backup one step */
+ *--tmp_p = '\0';
+ count--;
+ return count;
+ case '[':
+ tmp_count = get_list(regex, &r_list);
+ free(r_list);
+ /* In theory should not happen, but you never know
+ * what may happen in future ... */
+ if (-1 == tmp_count)
+ goto error;
+
+ /* Bail if we have a list */
+ if (tmp_count > 0) {
+ tmp_p[0] = '\0';
+ return count;
+ }
+ default:
+ *tmp_p++ = *regex++;
+ count++;
+ break;
+ }
+ }
+
+ tmp_p[0] = '\0';
+
+ return count;
+
+error:
+ free(*r_word);
+
+ return -1;
+}
+
+int match_word(regex_data_t *regex_data) {
+ char *data_p = regex_data->data;
+ char *r_word = NULL, *r_word_p;
+ size_t count = 0;
+
+ CHECK_REGEX_DATA_P(regex_data, exit);
+
+ count = get_word(regex_data->regex, &r_word);
+ if (-1 == count)
+ goto error;
+ if (0 == count)
+ goto exit;
+ r_word_p = r_word;
+
+ while ((strlen(data_p) > 0) && (strlen(r_word_p) > 0 )) {
+ /* If 'r_word' is not 100% part of 'string', we do not have
+ * a match. If its a '.', it matches no matter what. */
+ if ((data_p[0] != r_word_p[0]) && (r_word_p[0] != '.')) {
+ count = 0;
+ goto exit;
+ }
+
+ data_p++;
+ r_word_p++;
+ }
+
+ /* If 'string' is shorter than 'r_word', we do not have a match */
+ if ((0 == strlen(data_p)) && (0 < strlen(r_word_p))) {
+ count = 0;
+ goto exit;
+ }
+
+exit:
+ /* Fill in our structure */
+ if (0 == count)
+ regex_data->match = REGEX_NO_MATCH;
+ else if (strlen(regex_data->data) == count)
+ regex_data->match = REGEX_FULL_MATCH;
+ else
+ regex_data->match = REGEX_PARTIAL_MATCH;
+ if (regex_data->match != REGEX_NO_MATCH)
+ regex_data->where = regex_data->data;
+ else
+ regex_data->where = NULL;
+ regex_data->count = count;
+ regex_data->r_count = count;
+
+ free(r_word);
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+
+ free(r_word);
+ return -1;
+}
+
+size_t get_list_size(const char *regex) {
+ size_t count = 0;
+
+ /* NULL string means we do not have a list */
+ if ((NULL == regex) || (0 == strlen(regex)) || (regex[0] != '[')) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ regex++;
+
+ while ((strlen(regex) > 0) && (regex[0] != ']')) {
+ /* We have a sequence (x-y) */
+ if ((regex[0] == '-') && (regex[1] != ']') &&
+ (strlen(regex) >= 2) && (regex[-1] < regex[1]))
+ {
+ /* Add current + diff in sequence */
+ count += regex[1] - regex[-1];
+ /* Take care of '-' and next char */
+ regex += 2;
+ } else {
+ regex++;
+ count++;
+ }
+ }
+
+ return count;
+}
+
+size_t get_list(const char *regex, char **r_list) {
+ char *tmp_buf = NULL;
+ size_t count = 0;
+ size_t size;
+
+ /* NULL string means we do not have a list */
+ if ((NULL == regex) || (0 == strlen(regex))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ /* Bail if we do not have a list. Do not add debugging, as
+ * it is very noisy (used a lot when we call match_list() in
+ * __match() and match() to test for list matching) */
+ if (regex[0] != '[')
+ return 0;
+
+ size = get_list_size(regex);
+ if (0 == size) {
+ /* Should not be an issue, but just in case */
+ DBG_MSG("0 returned by get_list_size.\n");
+ return 0;
+ }
+
+ *r_list = malloc(size + 1);
+ if (NULL == *r_list) {
+ DBG_MSG("Failed to allocate buffer!\n");
+ return -1;
+ }
+ tmp_buf = *r_list;
+
+ /* Take care of '[' */
+ regex++;
+ count++;
+
+ while ((strlen(regex) > 0) && (regex[0] != ']')) {
+ /* We have a sequence (x-y) */
+ if ((regex[0] == '-') && (regex[1] != ']') &&
+ (strlen(regex) >= 2) && (regex[-1] < regex[1]))
+ {
+
+ /* Fill in missing chars in sequence */
+ while (tmp_buf[-1] < regex[1]) {
+ tmp_buf[0] = (char)(tmp_buf[-1] + 1);
+ tmp_buf++;
+ /* We do not increase count */
+ }
+ /* Take care of '-' and next char */
+ count += 2;
+ regex += 2;
+ } else {
+ *tmp_buf++ = *regex++;
+ count++;
+ }
+ }
+
+ tmp_buf[0] = '\0';
+ /* Take care of ']' */
+ count++;
+
+ /* We do not have a list as it does not end in ']' */
+ if (regex[0] != ']') {
+ count = 0;
+ free(*r_list);
+ }
+
+ return count;
+}
+
+/* If the first is the '^' character, everything but the list is matched
+ * NOTE: We only evaluate _ONE_ data character at a time!! */
+int __match_list(regex_data_t *regex_data) {
+ regex_data_t tmp_data;
+ char *data_p = regex_data->data;
+ char *list_p = regex_data->regex;
+ char test_regex[2] = { '\0', '\0' };
+ int invert = 0;
+ int match;
+ int retval;
+
+ CHECK_REGEX_DATA_P(regex_data, failed);
+
+ if (list_p[0] == '^') {
+ /* We need to invert the match */
+ invert = 1;
+ /* Make sure '^' is not part of our list */
+ list_p++;
+ }
+
+ if (invert)
+ /* All should be a match if not in the list */
+ match = 1;
+ else
+ /* We only have a match if in the list */
+ match = 0;
+
+ while (strlen(list_p) > 0) {
+ test_regex[0] = list_p[0];
+
+ FILL_REGEX_DATA(tmp_data, data_p, test_regex);
+ retval = match_word(&tmp_data);
+ if (-1 == retval)
+ goto error;
+
+ if (REGEX_MATCH(tmp_data)) {
+ if (invert)
+ /* If we exclude the list from
+ * characters we try to match, we
+ * have a match until one of the
+ * list is found. */
+ match = 0;
+ else
+ /* If not, we have to keep looking
+ * until one from the list match
+ * before we have a match */
+ match = 1;
+ break;
+ }
+ list_p++;
+ }
+
+ /* Fill in our structure */
+ if (match) {
+ regex_data->match = REGEX_PARTIAL_MATCH;
+ regex_data->where = regex_data->data;
+ regex_data->count = 1;
+ /* This one is more cosmetic, as match_list() will
+ * do the right thing */
+ regex_data->r_count = 0; /* strlen(regex_data->regex); */
+ } else {
+failed:
+ regex_data->match = REGEX_NO_MATCH;
+ regex_data->where = NULL;
+ regex_data->count = 0;
+ regex_data->r_count = 0;
+ }
+
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+
+ return -1;
+}
+
+int match_list(regex_data_t *regex_data) {
+ regex_data_t tmp_data;
+ char *data_p = regex_data->data;
+ char *list_p = regex_data->regex;
+ char *r_list = NULL;
+ size_t r_count = 0;
+ int retval;
+
+ CHECK_REGEX_DATA_P(regex_data, failed);
+
+ r_count = get_list(list_p, &r_list);
+ if (-1 == r_count)
+ goto error;
+ if (0 == r_count)
+ goto failed;
+
+ FILL_REGEX_DATA(tmp_data, data_p, &list_p[r_count-1]);
+ retval = __match_wildcard(&tmp_data, __match_list, r_list);
+ if (-1 == retval)
+ goto error;
+ if (REGEX_MATCH(tmp_data)) {
+ /* This should be 2 ('word' + 'wildcard'), so just remove
+ * the wildcard */
+ tmp_data.r_count--;
+ goto exit;
+ }
+
+ FILL_REGEX_DATA(tmp_data, data_p, r_list);
+ retval = __match_list(&tmp_data);
+ if (-1 == retval)
+ goto error;
+ if (REGEX_MATCH(tmp_data))
+ goto exit;
+
+failed:
+ /* We will fill in regex_data below */
+ tmp_data.match = REGEX_NO_MATCH;
+ tmp_data.where = NULL;
+ tmp_data.count = 0;
+ tmp_data.r_count = 0;
+
+exit:
+ /* Fill in our structure */
+ regex_data->match = tmp_data.match;
+ regex_data->where = tmp_data.where;
+ regex_data->count = tmp_data.count;
+ if (regex_data->match != REGEX_NO_MATCH)
+ /* tmp_data.r_count for __match_wildcard will take care of the
+ * wildcard, and tmp_data.r_count for __match_list will be 0 */
+ regex_data->r_count = r_count + tmp_data.r_count;
+ else
+ regex_data->r_count = 0;
+
+ free(r_list);
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+
+ free(r_list);
+ return -1;
+}
+
+size_t get_wildcard(const char *regex, char *r_wildcard) {
+ /* NULL regex means we do not have a wildcard */
+ if ((NULL == regex) || (0 == strlen(regex))) {
+ DBG_MSG("Invalid argument passed!\n");
+ return 0;
+ }
+
+ r_wildcard[0] = regex[0];
+ r_wildcard[2] = '\0';
+
+ switch (regex[1]) {
+ case '*':
+ case '+':
+ case '?':
+ r_wildcard[1] = regex[1];
+ break;
+ default:
+ r_wildcard[0] = '\0';
+ return 0;
+ }
+
+ return strlen(r_wildcard);
+}
+
+int __match_wildcard(regex_data_t *regex_data, int (*match_func)(regex_data_t *regex_data), const char *regex) {
+ regex_data_t tmp_data;
+ char *data_p = regex_data->data;
+ char *wildcard_p = regex_data->regex;
+ char r_wildcard[3];
+ size_t count = 0;
+ size_t r_count = 0;
+ int is_match = 0;
+ int retval;
+
+ CHECK_REGEX_DATA_P(regex_data, exit);
+
+ if (NULL == match_func) {
+ DBG_MSG("NULL match_func was passed!\n");
+ goto exit;
+ }
+
+ r_count = get_wildcard(wildcard_p, r_wildcard);
+ if (0 == r_count)
+ goto exit;
+
+ FILL_REGEX_DATA(tmp_data, data_p, (char *)regex);
+ retval = match_func(&tmp_data);
+ if (-1 == retval)
+ goto error;
+
+ switch (r_wildcard[1]) {
+ case '*':
+ case '?':
+ /* '*' and '?' always matches */
+ is_match = 1;
+ case '+':
+ /* We need to match all of them */
+ do {
+ /* If we have at least one match for '+', or none
+ * for '*' or '?', check if we have a word or list match.
+ * We do this because a word weights more than a wildcard */
+ if ((strlen(wildcard_p) > 2) && ((count > 0) ||
+ (r_wildcard[1] == '*') || (r_wildcard[1] == '?')))
+ {
+ regex_data_t tmp_data2;
+#if 0
+ printf("data_p = %s, wildcard_p = %s\n", data_p, wildcard_p);
+#endif
+
+ FILL_REGEX_DATA(tmp_data2, data_p, &wildcard_p[2]);
+ retval = match(&tmp_data2);
+ if (-1 == retval)
+ goto error;
+
+ if (/* '.' might be a special case ... */
+ /* (wildcard_p[2] != '.') && */
+ (REGEX_MATCH(tmp_data2) &&
+ (REGEX_FULL_MATCH == tmp_data2.match))) {
+ goto exit;
+ }
+ }
+
+ if (REGEX_MATCH(tmp_data)) {
+ data_p += tmp_data.count;
+ count += tmp_data.count;
+ is_match = 1;
+
+ FILL_REGEX_DATA(tmp_data, data_p, (char *)regex);
+ retval = match_func(&tmp_data);
+ if (-1 == retval)
+ goto error;
+ }
+ /* Only once for '?' */
+ } while ((REGEX_MATCH(tmp_data)) && (r_wildcard[1] != '?'));
+
+ break;
+ default:
+ /* No wildcard */
+ break;
+ }
+
+exit:
+ /* Fill in our structure */
+ /* We can still have a match ('*' and '?'), although count == 0 */
+ if ((0 == count) && (0 == is_match))
+ regex_data->match = REGEX_NO_MATCH;
+ else if (strlen(regex_data->data) == count)
+ regex_data->match = REGEX_FULL_MATCH;
+ else
+ regex_data->match = REGEX_PARTIAL_MATCH;
+ if (regex_data->match != REGEX_NO_MATCH)
+ regex_data->where = regex_data->data;
+ else
+ regex_data->where = NULL;
+ regex_data->count = count;
+ regex_data->r_count = r_count;
+
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+
+ return -1;
+}
+
+int match_wildcard(regex_data_t *regex_data) {
+ regex_data_t tmp_data;
+ char *data_p = regex_data->data;
+ char *wildcard_p = regex_data->regex;
+ char r_wildcard[3];
+ size_t r_count;
+ int retval;
+
+ CHECK_REGEX_DATA_P(regex_data, failed);
+
+ /* Invalid wildcard - we need a character + a regex operator */
+ if (strlen(wildcard_p) < 2)
+ goto failed;
+
+ r_count = get_wildcard(wildcard_p, r_wildcard);
+ if (0 == r_count)
+ goto failed;
+
+ /* Needed so that match_word() will not bail if it sees the wildcard */
+ r_wildcard[1] = '\0';
+
+ FILL_REGEX_DATA(tmp_data, data_p, wildcard_p);
+ retval = __match_wildcard(&tmp_data, match_word, r_wildcard);
+ if (-1 == retval)
+ goto error;
+ if (REGEX_MATCH(tmp_data))
+ goto exit;
+
+failed:
+ /* We will fill in regex_data below */
+ tmp_data.match = REGEX_NO_MATCH;
+ tmp_data.where = NULL;
+ tmp_data.count = 0;
+ tmp_data.r_count = 0;
+
+exit:
+ /* Fill in our structure */
+ regex_data->match = tmp_data.match;
+ regex_data->where = tmp_data.where;
+ regex_data->count = tmp_data.count;
+ regex_data->r_count = tmp_data.r_count;
+
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+
+ return -1;
+}
+
+int __match(regex_data_t *regex_data) {
+ regex_data_t tmp_data;
+ char *data_p = regex_data->data;
+ char *regex_p = regex_data->regex;
+ size_t count = 0;
+ size_t r_count = 0;
+ int match = 0;
+ int retval;
+
+ CHECK_REGEX_DATA_P(regex_data, failed);
+
+ while (strlen(regex_p) > 0) {
+#if 0
+ printf("data_p = '%s', regex_p = '%s'\n", data_p, regex_p);
+#endif
+
+ FILL_REGEX_DATA(tmp_data, data_p, regex_p);
+ retval = match_list(&tmp_data);
+ if (-1 == retval)
+ goto error;
+ if (REGEX_MATCH(tmp_data))
+ goto match;
+
+ FILL_REGEX_DATA(tmp_data, data_p, regex_p);
+ retval = match_wildcard(&tmp_data);
+ if (-1 == retval)
+ goto error;
+ if (REGEX_MATCH(tmp_data))
+ goto match;
+
+ FILL_REGEX_DATA(tmp_data, data_p, regex_p);
+ retval = match_word(&tmp_data);
+ if (-1 == retval)
+ goto error;
+ if (REGEX_MATCH(tmp_data))
+ goto match;
+
+ break;
+
+match:
+ data_p += tmp_data.count;
+ count += tmp_data.count;
+ regex_p += tmp_data.r_count;
+ r_count += tmp_data.r_count;
+ match = 1;
+
+ /* Check that we do not go out of bounds */
+ if (((data_p - regex_data->data) > strlen(regex_data->data)) ||
+ ((regex_p - regex_data->regex) > strlen(regex_data->regex)))
+ goto failed;
+ }
+
+ /* We could not match the whole regex (data too short?) */
+ if (0 != strlen(regex_p))
+ goto failed;
+
+ goto exit;
+
+failed:
+ /* We will fill in regex_data below */
+ count = 0;
+ r_count = 0;
+ match = 0;
+
+exit:
+ /* Fill in our structure */
+ /* We can still have a match ('*' and '?'), although count == 0 */
+ if ((0 == count) && (0 == match))
+ regex_data->match = REGEX_NO_MATCH;
+ else if (strlen(regex_data->data) == count)
+ regex_data->match = REGEX_FULL_MATCH;
+ else
+ regex_data->match = REGEX_PARTIAL_MATCH;
+ if (regex_data->match != REGEX_NO_MATCH)
+ regex_data->where = regex_data->data;
+ else
+ regex_data->where = NULL;
+ regex_data->count = count;
+ regex_data->r_count = r_count;
+
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+
+ return -1;
+}
+
+int match(regex_data_t *regex_data) {
+ regex_data_t tmp_data;
+ char *data_p = regex_data->data;
+ char *regex_p;
+ char *tmp_buf = NULL;
+ int from_start = 0;
+ int to_end = 0;
+ int retval;
+
+ CHECK_REGEX_DATA_P(regex_data, failed);
+
+ /* We might be modifying regex_p, so make a copy */
+ tmp_buf = strndup(regex_data->regex, strlen(regex_data->regex));
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ regex_p = tmp_buf;
+
+ /* Should we only match from the start? */
+ if (regex_p[0] == '^') {
+ regex_p++;
+ from_start = 1;
+ }
+
+ /* Should we match up to the end? */
+ if (regex_p[strlen(regex_p) - 1] == '$') {
+ regex_p[strlen(regex_p) - 1] = '\0';
+ to_end = 1;
+ }
+
+ do {
+ FILL_REGEX_DATA(tmp_data, data_p, regex_p);
+ retval = __match(&tmp_data);
+ if (-1 == retval)
+ goto error;
+ } while ((strlen(data_p++) > 0) &&
+ (!REGEX_MATCH(tmp_data)) && (0 == from_start));
+
+ /* Compensate for above extra inc */
+ data_p--;
+
+ /* Fill in our structure */
+ if (REGEX_MATCH(tmp_data)) {
+ /* Check if we had an '$' at the end of the regex, and
+ * verify that we still have a match */
+ if ((1 == to_end) && (tmp_data.count != strlen(data_p))) {
+ goto failed;
+ }
+
+ if ((data_p == regex_data->data) &&
+ (tmp_data.match == REGEX_FULL_MATCH))
+ regex_data->match = REGEX_FULL_MATCH;
+ else
+ regex_data->match = REGEX_PARTIAL_MATCH;
+ regex_data->where = data_p;
+ regex_data->count = tmp_data.count;
+ regex_data->r_count = tmp_data.r_count;
+ if (1 == from_start)
+ regex_data->r_count++;
+ if (1 == to_end)
+ regex_data->r_count++;
+ } else {
+failed:
+ regex_data->match = REGEX_NO_MATCH;
+ regex_data->where = NULL;
+ regex_data->count = 0;
+ regex_data->r_count = 0;
+ }
+
+ free(tmp_buf);
+
+ return 0;
+
+error:
+ regex_data->match = REGEX_NO_MATCH;
+ free(tmp_buf);
+
+ return -1;
+}
+
+#if 0
+int main() {
+ regex_data_t tmp_data;
+ FILE *rcscript;
+ char regex[] = "^[ \t]*[d-p]+d[ \t]*(+)[ \t]*{$";
+ char tempstr[255];
+ int retval;
+
+ rcscript = fopen("acpid", "r");
+ if (NULL == rcscript) {
+ printf("%s", "Error opening file!");
+ return 1;
+ }
+
+ while (0 != fgets(tempstr, 254, rcscript)) {
+ if (tempstr[strlen(tempstr) - 1] == '\n')
+ tempstr[strlen(tempstr) - 1] = '\0';
+
+ FILL_REGEX_DATA(tmp_data, tempstr, regex);
+ retval = match(&tmp_data);
+ if (-1 != retval) {
+ if (REGEX_MATCH(tmp_data)) {
+ printf("*** string = '%s' ***\n", tempstr);
+ printf("*** regex = '%s' ***\n", regex);
+
+ if (REGEX_FULL_MATCH == tmp_data.match)
+ printf("match (full): '%s', %i\n", tmp_data.where, tmp_data.count);
+ else
+ printf("match: '%s', %i\n", tmp_data.where, tmp_data.count);
+
+ } else {
+ printf("%s", "No match\n");
+ }
+ } else {
+ printf("%s", "Error during match\n");
+ }
+ }
+
+ fclose(rcscript);
+
+ return 0;
+}
+#endif
+
diff --git a/src/core/simple-regex.h b/src/core/simple-regex.h
new file mode 100644
index 0000000..affa312
--- /dev/null
+++ b/src/core/simple-regex.h
@@ -0,0 +1,86 @@
+/*
+ * simple_regex.h
+ *
+ * Simle regex library.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#ifndef _SIMPLE_REGEX_H
+#define _SIMPLE_REGEX_H
+
+#define REGEX_NO_MATCH 0 /* We have no match */
+#define REGEX_PARTIAL_MATCH 1 /* Some of the string matches the regex */
+#define REGEX_FULL_MATCH 2 /* The whole string matches the regex */
+
+/* Macro to fill in .data and .regex */
+#define FILL_REGEX_DATA(_regex_data, _string, _regex) \
+ do { \
+ _regex_data.data = _string; \
+ _regex_data.regex = _regex; \
+ } while (0)
+
+/* Fill in _regex_data with _data and _regex, on failure goto _error */
+#define DO_REGEX(_regex_data, _data, _regex, _error) \
+ do { \
+ FILL_REGEX_DATA(_regex_data, _data, _regex); \
+ if (-1 == match(&_regex_data)) { \
+ DBG_MSG("Could not do regex match!\n"); \
+ goto _error; \
+ } \
+ } while (0)
+
+/* Evaluate to true if we have some kind of match */
+#define REGEX_MATCH(_regex_data) \
+ ((REGEX_FULL_MATCH == _regex_data.match) || \
+ (REGEX_PARTIAL_MATCH == _regex_data.match))
+
+/* Same as above, but for use when _regex_data is a pointer */
+#define REGEX_MATCH_P(_regex_data) \
+ ((REGEX_FULL_MATCH == _regex_data->match) || \
+ (REGEX_PARTIAL_MATCH == _regex_data->match))
+
+typedef struct {
+ char *data; /* String to perform regex operation on */
+ char *regex; /* String containing regex to use */
+ int match; /* Will be set if there was a match. Check
+ * REGEX_*_MATCH above for possible values */
+ char *where; /* Pointer to where match starts in data */
+ size_t count; /* Count characters from data matched by regex */
+ size_t r_count; /* Count characters of regex used for match. This
+ * should normally be the lenght of regex, but might
+ * not be for some internal functions ... */
+} regex_data_t;
+
+/*
+ * Return:
+ *
+ * 0 - There was no error. If there was a match, regex_data->match
+ * - will be > 0 (this is the definitive check - if not true, the
+ * - other values of the struct may be bogus), regex_data->count
+ * - will be the amount of data that was matched (might be 0 for
+ * - some wildcards), and regex_data->r_count will be > 0.
+ *
+ * -1 - An error occured. Check errno for more info.
+ *
+ */
+int match(regex_data_t *regex_data);
+
+#endif /* _SIMPLE_REGEX_H */
+
diff --git a/src/core/test-regex.c b/src/core/test-regex.c
new file mode 100644
index 0000000..610a490
--- /dev/null
+++ b/src/core/test-regex.c
@@ -0,0 +1,80 @@
+/*
+ * test-regex.c
+ *
+ * Test for the simple-regex module.
+ *
+ * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.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 version 2 of the License.
+ *
+ * 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.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Header$
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "simple-regex.h"
+
+char *test_data[] = {
+ /* string, pattern, match (1 = yes, 0 = no) */
+ "ab", "a?[ab]b", "1",
+ "abb", "a?[ab]b", "1",
+ "aab", "a?[ab]b", "1",
+ "a", "a?a?a?a", "1",
+ "aa", "a?a?a?a", "1",
+ "aa", "a?a?a?aa", "1",
+ "aaa", "a?a?a?aa", "1",
+ "ab", "[ab]*", "1",
+ "abc", "[ab]*.", "1",
+ "ab", "[ab]*b+", "1",
+ "ab", "a?[ab]*b+", "1",
+ "aaaaaaaaaaaaaaaaaaaaaaa", "a*b", "0",
+ "aaaaaaaaabaaabbaaaaaa", "a*b+a*b*ba+", "1",
+ "ababababab", "a.*", "1",
+ "baaaaaaaab", "a*", "0",
+ NULL
+};
+
+int main() {
+ regex_data_t tmp_data;
+ char buf[256], string[100], regex[100];
+ int i;
+
+ for (i = 0; NULL != test_data[i]; i += 3) {
+ snprintf(string, 99, "'%s'", test_data[i]);
+ snprintf(regex, 99, "'%s'", test_data[i + 1]);
+ snprintf(buf, 255, "string = %s, pattern = %s", string, regex);
+ printf("%-60s", buf);
+ DO_REGEX(tmp_data, test_data[i], test_data[i + 1], error);
+ if (REGEX_MATCH(tmp_data) && (REGEX_FULL_MATCH == tmp_data.match)) {
+ if (0 != strncmp(test_data[i + 2], "1", 1))
+ goto error;
+ } else {
+ if (0 != strncmp(test_data[i + 2], "0", 1))
+ goto error;
+ }
+
+ printf("%s\n", "[ \033[32;01mOK\033[0m ]");
+ }
+
+ return 0;
+error:
+ printf("%s\n", "[ \033[31;01m!!\033[0m ]");
+
+ return 1;
+}
diff --git a/src/env_whitelist b/src/env_whitelist
new file mode 100644
index 0000000..00ea17a
--- /dev/null
+++ b/src/env_whitelist
@@ -0,0 +1,26 @@
+# /lib/rcscripts/conf.d/env_whitelist: System environment whitelist for rc-system
+
+# See /etc/conf.d/env_whitelist for details.
+
+#
+# Internal variables needed for operation of rc-system
+#
+# NB: Do not modify below this line if you do not know what you are doing!!
+#
+
+# Hotplug ?
+IN_BACKGROUND
+IN_HOTPLUG
+
+# Default shell stuff
+SHELL
+USER
+HOME
+
+# From /sbin/init
+PATH
+INIT_VERSION
+RUNLEVEL
+PREVLEVEL
+CONSOLE
+
diff --git a/src/filefuncs/Makefile b/src/filefuncs/Makefile
new file mode 100644
index 0000000..4d35197
--- /dev/null
+++ b/src/filefuncs/Makefile
@@ -0,0 +1,17 @@
+CC = gcc
+LD = gcc
+
+TARGETS = filefuncs.so
+
+all: $(TARGETS)
+
+filefuncs.o: filefuncs.c
+ $(CC) -shared -Wall -DHAVE_CONFIG_H -c -O -fPIC -I/usr/include/awk $^
+
+filefuncs.so: filefuncs.o
+ $(LD) -o $@ -shared $^
+
+clean:
+ rm -f $(TARGETS)
+ rm -f *.o *~ core
+
diff --git a/src/filefuncs/filefuncs.c b/src/filefuncs/filefuncs.c
new file mode 100644
index 0000000..8f3cc34
--- /dev/null
+++ b/src/filefuncs/filefuncs.c
@@ -0,0 +1,486 @@
+/*
+ * filefuncs.c - Builtin functions that provide initial minimal iterface
+ * to the file system.
+ *
+ * Arnold Robbins, update for 3.1, Mon Nov 23 12:53:39 EST 1998
+ */
+
+/*
+ * Copyright (C) 2001 the Free Software Foundation, Inc.
+ *
+ * This file is part of GAWK, the GNU implementation of the
+ * AWK Programming Language.
+ *
+ * GAWK 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.
+ *
+ * GAWK 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
+ */
+
+/*
+ * Copyright 1999-2004 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ * Author: Martin Schlemmer <azarah@gentoo.org>, Nov 2002
+ * $Header$
+ *
+ * Extended with: do_symlink()
+ * do_unlink()
+ * do_mkdir()
+ * do_rmdir()
+ *
+ * for use in the Gentoo rcscripts
+ *
+ */
+
+#include "awk.h"
+
+#include <unistd.h>
+#include <sys/sysmacros.h>
+
+/* do_chdir --- provide dynamically loaded chdir() builtin for gawk */
+
+static NODE *
+do_chdir(tree)
+NODE *tree;
+{
+ NODE *newdir;
+ int ret = -1;
+
+ if (do_lint && tree->param_cnt > 1)
+ lintwarn("chdir: called with too many arguments");
+
+ newdir = get_argument(tree, 0);
+ if (newdir != NULL) {
+ (void) force_string(newdir);
+ ret = chdir(newdir->stptr);
+ if (ret < 0)
+ update_ERRNO();
+
+ free_temp(newdir);
+ } else if (do_lint)
+ lintwarn("chdir: called with no arguments");
+
+
+ /* Set the return value */
+ set_value(tmp_number((AWKNUM) ret));
+
+ /* Just to make the interpreter happy */
+ return tmp_number((AWKNUM) 0);
+}
+
+/* do_symlink --- provide dynamically loaded symlink() builtin for gawk */
+
+static NODE *
+do_symlink(tree)
+NODE *tree;
+{
+ NODE *oldpath, *newpath;
+ int ret = -1;
+
+ if (do_lint && tree->param_cnt > 2)
+ lintwarn("symlink: called with too many arguments");
+
+ oldpath = get_argument(tree, 0);
+ newpath = get_argument(tree, 1);
+ if ((oldpath != NULL) && (newpath)) {
+ (void) force_string(oldpath);
+ (void) force_string(newpath);
+ ret = symlink(oldpath->stptr, newpath->stptr);
+ if (ret < 0)
+ update_ERRNO();
+
+ free_temp(oldpath);
+ free_temp(newpath);
+ } else if (do_lint)
+ lintwarn("symlink: called with not enough arguments");
+
+ /* Set the return value */
+ set_value(tmp_number((AWKNUM) ret));
+
+ /* Just to make the interpreter happy */
+ return tmp_number((AWKNUM) 0);
+}
+
+/* do_unlink --- provide dynamically loaded unlink() builtin for gawk */
+
+static NODE *
+do_unlink(tree)
+NODE *tree;
+{
+ NODE *pathname;
+ int ret = -1;
+
+ if (do_lint && tree->param_cnt > 1)
+ lintwarn("unlink: called with too many arguments");
+
+ pathname = get_argument(tree, 0);
+ if (pathname != NULL) {
+ (void) force_string(pathname);
+ ret = unlink(pathname->stptr);
+ if (ret < 0)
+ update_ERRNO();
+
+ free_temp(pathname);
+ } else if (do_lint)
+ lintwarn("unlink: called with no arguments");
+
+ /* Set the return value */
+ set_value(tmp_number((AWKNUM) ret));
+
+ /* Just to make the interpreter happy */
+ return tmp_number((AWKNUM) 0);
+}
+
+/* do_mkdir --- provide dynamically loaded mkdir() builtin for gawk */
+
+static NODE *
+do_mkdir(tree)
+NODE *tree;
+{
+ NODE *pathname, *mode;
+ int ret = -1;
+
+ if (do_lint && tree->param_cnt > 2)
+ lintwarn("mkdir: called with too many arguments");
+
+ pathname = get_argument(tree, 0);
+ mode = get_argument(tree, 1);
+ if ((pathname != NULL) && (mode != NULL)) {
+ (void) force_string(pathname);
+ (void) force_number(mode);
+ ret = mkdir(pathname->stptr, mode->numbr);
+ if (ret < 0)
+ update_ERRNO();
+
+ free_temp(pathname);
+ free_temp(mode);
+ } else if (do_lint)
+ lintwarn("mkdir: called with not enough arguments");
+
+ /* Set the return value */
+ set_value(tmp_number((AWKNUM) ret));
+
+ /* Just to make the interpreter happy */
+ return tmp_number((AWKNUM) 0);
+}
+
+/* do_rmdir --- provide dynamically loaded rmdir() builtin for gawk */
+
+static NODE *
+do_rmdir(tree)
+NODE *tree;
+{
+ NODE *pathname;
+ int ret = -1;
+
+ if (do_lint && tree->param_cnt > 1)
+ lintwarn("rmdir: called with too many arguments");
+
+ pathname = get_argument(tree, 0);
+ if (pathname != NULL) {
+ (void) force_string(pathname);
+ ret = rmdir(pathname->stptr);
+ if (ret < 0)
+ update_ERRNO();
+
+ free_temp(pathname);
+ } else if (do_lint)
+ lintwarn("rmdir: called with no arguments");
+
+ /* Set the return value */
+ set_value(tmp_number((AWKNUM) ret));
+
+ /* Just to make the interpreter happy */
+ return tmp_number((AWKNUM) 0);
+}
+
+/* format_mode --- turn a stat mode field into something readable */
+
+static char *
+format_mode(fmode)
+unsigned long fmode;
+{
+ static char outbuf[12];
+ int i;
+
+ strcpy(outbuf, "----------");
+ /* first, get the file type */
+ i = 0;
+ switch (fmode & S_IFMT) {
+#ifdef S_IFSOCK
+ case S_IFSOCK:
+ outbuf[i] = 's';
+ break;
+#endif
+#ifdef S_IFLNK
+ case S_IFLNK:
+ outbuf[i] = 'l';
+ break;
+#endif
+ case S_IFREG:
+ outbuf[i] = '-'; /* redundant */
+ break;
+ case S_IFBLK:
+ outbuf[i] = 'b';
+ break;
+ case S_IFDIR:
+ outbuf[i] = 'd';
+ break;
+#ifdef S_IFDOOR /* Solaris weirdness */
+ case S_IFDOOR:
+ outbuf[i] = 'D';
+ break;
+#endif /* S_IFDOOR */
+ case S_IFCHR:
+ outbuf[i] = 'c';
+ break;
+#ifdef S_IFIFO
+ case S_IFIFO:
+ outbuf[i] = 'p';
+ break;
+#endif
+ }
+
+ i++;
+ if ((fmode & S_IRUSR) != 0)
+ outbuf[i] = 'r';
+ i++;
+ if ((fmode & S_IWUSR) != 0)
+ outbuf[i] = 'w';
+ i++;
+ if ((fmode & S_IXUSR) != 0)
+ outbuf[i] = 'x';
+ i++;
+
+ if ((fmode & S_IRGRP) != 0)
+ outbuf[i] = 'r';
+ i++;
+ if ((fmode & S_IWGRP) != 0)
+ outbuf[i] = 'w';
+ i++;
+ if ((fmode & S_IXGRP) != 0)
+ outbuf[i] = 'x';
+ i++;
+
+ if ((fmode & S_IROTH) != 0)
+ outbuf[i] = 'r';
+ i++;
+ if ((fmode & S_IWOTH) != 0)
+ outbuf[i] = 'w';
+ i++;
+ if ((fmode & S_IXOTH) != 0)
+ outbuf[i] = 'x';
+ i++;
+
+ outbuf[i] = '\0';
+
+ if ((fmode & S_ISUID) != 0) {
+ if (outbuf[3] == 'x')
+ outbuf[3] = 's';
+ else
+ outbuf[3] = 'S';
+ }
+
+ /* setgid without execute == locking */
+ if ((fmode & S_ISGID) != 0) {
+ if (outbuf[6] == 'x')
+ outbuf[6] = 's';
+ else
+ outbuf[6] = 'l';
+ }
+
+ if ((fmode & S_ISVTX) != 0) {
+ if (outbuf[9] == 'x')
+ outbuf[9] = 't';
+ else
+ outbuf[9] = 'T';
+ }
+
+ return outbuf;
+}
+
+/* do_stat --- provide a stat() function for gawk */
+
+static NODE *
+do_stat(tree)
+NODE *tree;
+{
+ NODE *file, *array;
+ struct stat sbuf;
+ int ret;
+ NODE **aptr;
+ char *pmode; /* printable mode */
+ char *type = "unknown";
+
+ /* check arg count */
+ if (tree->param_cnt != 2)
+ fatal(
+ "stat: called with incorrect number of arguments (%d), should be 2",
+ tree->param_cnt);
+
+ /* directory is first arg, array to hold results is second */
+ file = get_argument(tree, 0);
+ array = get_argument(tree, 1);
+
+ /* empty out the array */
+ assoc_clear(array);
+
+ /* lstat the file, if error, set ERRNO and return */
+ (void) force_string(file);
+ ret = lstat(file->stptr, & sbuf);
+ if (ret < 0) {
+ update_ERRNO();
+
+ set_value(tmp_number((AWKNUM) ret));
+
+ free_temp(file);
+ return tmp_number((AWKNUM) 0);
+ }
+
+ /* fill in the array */
+ aptr = assoc_lookup(array, tmp_string("name", 4), FALSE);
+ *aptr = dupnode(file);
+
+ aptr = assoc_lookup(array, tmp_string("dev", 3), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_dev);
+
+ aptr = assoc_lookup(array, tmp_string("ino", 3), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_ino);
+
+ aptr = assoc_lookup(array, tmp_string("mode", 4), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_mode);
+
+ aptr = assoc_lookup(array, tmp_string("nlink", 5), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_nlink);
+
+ aptr = assoc_lookup(array, tmp_string("uid", 3), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_uid);
+
+ aptr = assoc_lookup(array, tmp_string("gid", 3), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_gid);
+
+ aptr = assoc_lookup(array, tmp_string("size", 4), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_size);
+
+ aptr = assoc_lookup(array, tmp_string("blocks", 6), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_blocks);
+
+ aptr = assoc_lookup(array, tmp_string("atime", 5), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_atime);
+
+ aptr = assoc_lookup(array, tmp_string("mtime", 5), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_mtime);
+
+ aptr = assoc_lookup(array, tmp_string("ctime", 5), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_ctime);
+
+ /* for block and character devices, add rdev, major and minor numbers */
+ if (S_ISBLK(sbuf.st_mode) || S_ISCHR(sbuf.st_mode)) {
+ aptr = assoc_lookup(array, tmp_string("rdev", 4), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_rdev);
+
+ aptr = assoc_lookup(array, tmp_string("major", 5), FALSE);
+ *aptr = make_number((AWKNUM) major(sbuf.st_rdev));
+
+ aptr = assoc_lookup(array, tmp_string("minor", 5), FALSE);
+ *aptr = make_number((AWKNUM) minor(sbuf.st_rdev));
+ }
+
+#ifdef HAVE_ST_BLKSIZE
+ aptr = assoc_lookup(array, tmp_string("blksize", 7), FALSE);
+ *aptr = make_number((AWKNUM) sbuf.st_blksize);
+#endif /* HAVE_ST_BLKSIZE */
+
+ aptr = assoc_lookup(array, tmp_string("pmode", 5), FALSE);
+ pmode = format_mode(sbuf.st_mode);
+ *aptr = make_string(pmode, strlen(pmode));
+
+ /* for symbolic links, add a linkval field */
+ if (S_ISLNK(sbuf.st_mode)) {
+ char buf[BUFSIZ*2];
+ int linksize;
+
+ linksize = readlink(file->stptr, buf, sizeof buf);
+ /* should make this smarter */
+ if (linksize == sizeof(buf))
+ fatal("size of symbolic link too big");
+ buf[linksize] = '\0';
+
+ aptr = assoc_lookup(array, tmp_string("linkval", 7), FALSE);
+ *aptr = make_string(buf, linksize);
+ }
+
+ /* add a type field */
+ switch (sbuf.st_mode & S_IFMT) {
+#ifdef S_IFSOCK
+ case S_IFSOCK:
+ type = "socket";
+ break;
+#endif
+#ifdef S_IFLNK
+ case S_IFLNK:
+ type = "symlink";
+ break;
+#endif
+ case S_IFREG:
+ type = "file";
+ break;
+ case S_IFBLK:
+ type = "blockdev";
+ break;
+ case S_IFDIR:
+ type = "directory";
+ break;
+#ifdef S_IFDOOR
+ case S_IFDOOR:
+ type = "door";
+ break;
+#endif
+ case S_IFCHR:
+ type = "chardev";
+ break;
+#ifdef S_IFIFO
+ case S_IFIFO:
+ type = "fifo";
+ break;
+#endif
+ }
+
+ aptr = assoc_lookup(array, tmp_string("type", 4), FALSE);
+ *aptr = make_string(type, strlen(type));
+
+ free_temp(file);
+
+ /* Set the return value */
+ set_value(tmp_number((AWKNUM) ret));
+
+ /* Just to make the interpreter happy */
+ return tmp_number((AWKNUM) 0);
+}
+
+/* dlload --- load new builtins in this library */
+
+NODE *
+dlload(tree, dl)
+NODE *tree;
+void *dl;
+{
+ make_builtin("chdir", do_chdir, 1);
+ make_builtin("symlink", do_symlink, 2);
+ make_builtin("unlink", do_unlink, 1);
+ make_builtin("mkdir", do_mkdir, 2);
+ make_builtin("rmdir", do_rmdir, 1);
+ make_builtin("stat", do_stat, 2);
+
+ return tmp_number((AWKNUM) 0);
+}
+
diff --git a/src/headers.h b/src/headers.h
new file mode 100644
index 0000000..09f07a1
--- /dev/null
+++ b/src/headers.h
@@ -0,0 +1,26 @@
+/*
+ * header.h
+ * Dirty little file to include header files w/out autotools.
+ *
+ * Copyright 1999-2004 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ * $Header$
+ */
+
+/* Common includes */
+#define HAVE_TIOCNOTTY
+#define HAVE_SETSID
+
+/* OS-specific includes */
+#if defined(__GLIBC__)
+# define HAVE_SYS_SYSMACROS_H
+# define HAVE_ERROR_H
+#endif
+
+/* Now we actually include crap ;) */
+#ifdef HAVE_ERROR_H
+# include <error.h>
+#endif
+#ifdef HAVE_SYS_SYSMACROS_H
+# include <sys/sysmacros.h>
+#endif
diff --git a/src/runscript.c b/src/runscript.c
new file mode 100644
index 0000000..61b43bf
--- /dev/null
+++ b/src/runscript.c
@@ -0,0 +1,250 @@
+/*
+ * runscript.c
+ * Handle launching of Gentoo init scripts.
+ *
+ * Copyright 1999-2004 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ * $Header$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dlfcn.h>
+
+#include "core/debug.h"
+#include "core/misc.h"
+
+#ifndef LIBDIR
+# define LIBDIR "lib"
+#endif
+
+#define SBIN_RC "/sbin/rc"
+#define PROFILE_ENV "/etc/profile.env"
+#define RCSCRIPTS_LIB "/" LIBDIR "/rcscripts"
+#define SYS_WHITELIST RCSCRIPTS_LIB "/conf.d/env_whitelist"
+#define USR_WHITELIST "/etc/conf.d/env_whitelist"
+#define RCSCRIPT_HELP RCSCRIPTS_LIB "/sh/rc-help.sh"
+#define SELINUX_LIB RCSCRIPTS_LIB "/runscript_selinux.so"
+#define SOFTLEVEL "SOFTLEVEL"
+
+#define DEFAULT_PATH "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin"
+
+#define IS_SBIN_RC() (0 == strcmp(caller, SBIN_RC))
+
+static void (*selinux_run_init_old) (void);
+static void (*selinux_run_init_new) (int argc, char **argv);
+
+extern char **environ;
+
+void setup_selinux(int argc, char **argv) {
+ void *lib_handle = NULL;
+
+ lib_handle = dlopen(SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL);
+ if (NULL != lib_handle) {
+ selinux_run_init_old = dlsym(lib_handle, "selinux_runscript");
+ selinux_run_init_new = dlsym(lib_handle, "selinux_runscript2");
+
+ /* Use new run_init if it exists, else fall back to old */
+ if (NULL != selinux_run_init_new)
+ selinux_run_init_new(argc, argv);
+ else if (NULL != selinux_run_init_old)
+ selinux_run_init_old();
+ else {
+ /* This shouldnt happen... probably corrupt lib */
+ fprintf(stderr, "Run_init is missing from runscript_selinux.so!\n");
+ exit(127);
+ }
+ }
+}
+
+char **get_whitelist(char **whitelist, char *filename) {
+ char *buf = NULL;
+ char *tmp_buf = NULL;
+ char *tmp_p = NULL;
+ char *token = NULL;
+ size_t lenght = 0;
+ int count = 0;
+ int current = 0;
+
+ if (-1 == file_map(filename, &buf, &lenght))
+ return NULL;
+
+ while (current < lenght) {
+ count = buf_get_line(buf, lenght, current);
+
+ tmp_buf = strndup(&buf[current], count);
+ if (NULL == tmp_buf) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ tmp_p = tmp_buf;
+
+ /* Strip leading spaces/tabs */
+ while ((tmp_p[0] == ' ') || (tmp_p[0] == '\t'))
+ tmp_p++;
+
+ /* Get entry - we do not want comments, and only the first word
+ * on a line is valid */
+ token = strsep(&tmp_p, "# \t");
+ if (NULL != token && '\0' != token[0]) {
+ tmp_p = strndup(token, strlen(token));
+ STRING_LIST_ADD(whitelist, tmp_p, error);
+ }
+
+ current += count + 1;
+ free(tmp_buf);
+ /* Set to NULL in case we error out above and have
+ * to free below */
+ tmp_buf = NULL;
+ }
+
+
+ file_unmap(buf, lenght);
+
+ return whitelist;
+
+error:
+ if (NULL != tmp_buf)
+ free(tmp_buf);
+ file_unmap(buf, lenght);
+ STRING_LIST_FREE(whitelist);
+
+ return NULL;
+}
+
+char **filter_environ(char *caller) {
+ char **myenv = NULL;
+ char **whitelist = NULL;
+ char *env_name = NULL;
+ int check_profile = 1;
+ int count = 0;
+
+ if (NULL != getenv(SOFTLEVEL) && !IS_SBIN_RC())
+ /* Called from /sbin/rc, but not /sbin/rc itself, so current
+ * environment should be fine */
+ return environ;
+
+ if (1 == is_file(SYS_WHITELIST, 1))
+ whitelist = get_whitelist(whitelist, SYS_WHITELIST);
+ else
+ EWARN("System environment whitelist missing!\n");
+
+ if (1 == is_file(USR_WHITELIST, 1))
+ whitelist = get_whitelist(whitelist, USR_WHITELIST);
+
+ if (NULL == whitelist)
+ /* If no whitelist is present, revert to old behaviour */
+ return environ;
+
+ if (1 != is_file(PROFILE_ENV, 1))
+ /* XXX: Maybe warn here? */
+ check_profile = 0;
+
+ STRING_LIST_FOR_EACH(whitelist, env_name, count) {
+ char *env_var = NULL;
+ char *tmp_p = NULL;
+ int env_len = 0;
+
+ env_var = getenv(env_name);
+ if (NULL != env_var)
+ goto add_entry;
+
+ if (1 == check_profile) {
+ char *tmp_env_name = NULL;
+ int tmp_len = 0;
+
+ /* The entries in PROFILE_ENV is of the form:
+ * export VAR_NAME=value */
+ tmp_len = strlen(env_name) + strlen("export ") + 1;
+ tmp_env_name = calloc(tmp_len, sizeof(char *));
+ if (NULL == tmp_env_name) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ snprintf(tmp_env_name, tmp_len, "export %s", env_name);
+
+ /* Clear errno so that subsequent calls do not trigger
+ * DBG_MSG */
+ errno = 0;
+ env_var = get_cnf_entry(PROFILE_ENV, tmp_env_name);
+ free(tmp_env_name);
+ if (NULL == env_var && ENOMSG != errno)
+ goto error;
+ else if (NULL != env_var)
+ goto add_entry;
+ }
+
+ continue;
+
+add_entry:
+ env_len = strlen(env_name) + strlen(env_var) + 2;
+ tmp_p = calloc(env_len, sizeof(char *));
+ if (NULL == tmp_p) {
+ DBG_MSG("Failed to allocate temporary buffer!\n");
+ goto error;
+ }
+ snprintf(tmp_p, env_len, "%s=%s", env_name, env_var);
+ STRING_LIST_ADD(myenv, tmp_p, error);
+ }
+
+ STRING_LIST_FREE(whitelist);
+
+ if (NULL == myenv)
+ /* If all else fails, just add a default PATH */
+ STRING_LIST_ADD(myenv, strdup(DEFAULT_PATH), error);
+
+ return myenv;
+
+error:
+ STRING_LIST_FREE(myenv);
+ STRING_LIST_FREE(whitelist);
+
+ return NULL;
+}
+
+int main(int argc, char *argv[]) {
+ char *myargs[32];
+ char **myenv = NULL;
+ char *caller = argv[1];
+ int new = 1;
+
+ myargs[0] = "runscript";
+ while (argv[new] != 0) {
+ myargs[new] = argv[new];
+ new++;
+ }
+ myargs[new] = NULL;
+
+ /* Do not do help for /sbin/rc */
+ if (argc < 3 && !IS_SBIN_RC()) {
+ execv(RCSCRIPT_HELP, myargs);
+ exit(1);
+ }
+
+ /* Setup a filtered environment according to the whitelist */
+ myenv = filter_environ(caller);
+ if (NULL == myenv) {
+ EWARN("%s: Failed to filter the environment!\n", caller);
+ /* XXX: Might think to bail here, but it could mean the system
+ * is rendered unbootable, so rather not */
+ myenv = environ;
+ }
+
+ /* Ok, we are ready to go, so setup selinux if applicable */
+ setup_selinux(argc, argv);
+
+ if (!IS_SBIN_RC()) {
+ if (execve("/sbin/runscript.sh", myargs, myenv) < 0)
+ exit(1);
+ } else {
+ if (execve("/bin/bash", myargs, myenv) < 0)
+ exit(1);
+ }
+
+ return 0;
+}
diff --git a/src/start-stop-daemon.c b/src/start-stop-daemon.c
new file mode 100644
index 0000000..315164f
--- /dev/null
+++ b/src/start-stop-daemon.c
@@ -0,0 +1,1375 @@
+/*
+ * A rewrite of the original Debian's start-stop-daemon Perl script
+ * in C (faster - it is executed many times during system startup).
+ *
+ * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>,
+ * public domain. Based conceptually on start-stop-daemon.pl, by Ian
+ * Jackson <ijackson@gnu.ai.mit.edu>. May be used and distributed
+ * freely for any purpose. Changes by Christian Schwarz
+ * <schwarz@monet.m.isar.de>, to make output conform to the Debian
+ * Console Message Standard, also placed in public domain. Minor
+ * changes by Klee Dienes <klee@debian.org>, also placed in the Public
+ * Domain.
+ *
+ * Changes by Ben Collins <bcollins@debian.org>, added --chuid, --background
+ * and --make-pidfile options, placed in public domain aswell.
+ *
+ * Port to OpenBSD by Sontri Tomo Huynh <huynh.29@osu.edu>
+ * and Andreas Schuldei <andreas@schuldei.org>
+ *
+ * Changes by Ian Jackson: added --retry (and associated rearrangements).
+ *
+ * Modified for Gentoo rc-scripts by Donny Davies <woodchip@gentoo.org>:
+ * I removed the BSD/Hurd/OtherOS stuff, added #include <stddef.h>
+ * and stuck in a #define VERSION "1.9.18". Now it compiles without
+ * the whole automake/config.h dance.
+ *
+ * Updated by Aron Griffis <agriffis@gentoo.org>:
+ * Fetched updates from Debian's dpkg-1.10.20, including fix for
+ * Gentoo bug 22686 (start-stop-daemon in baselayout doesn't allow
+ * altered nicelevel).
+ * Updated by Kito <kito@gentoo.org>:
+ * Add support for Darwin, additional patches from opendarwin.org
+ * fix for Gentoo bug 72145 from eldad@gentoo.org
+ */
+
+#define VERSION "1.10.20"
+#include <stddef.h>
+
+#define NONRETURNPRINTFFORMAT(x, y) \
+ __attribute__((noreturn, format(printf, x, y)))
+#define NONRETURNING \
+ __attribute__((noreturn))
+
+#if defined(linux) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__))
+# define OSLinux
+#elif defined(__GNU__)
+# define OSHURD
+#elif defined(__sparc__)
+# define OSsunos
+#elif defined(OPENBSD) || defined(__OpenBSD__)
+# define OSOpenBSD
+#elif defined(hpux)
+# define OShpux
+#elif defined(__FreeBSD__)
+# define OSFreeBSD
+#elif defined(__NetBSD__)
+# define OSNetBSD
+#elif defined(__APPLE__)
+# define OSDarwin
+#else
+# error Unknown architecture - cannot build start-stop-daemon
+#endif
+
+#define MIN_POLL_INTERVAL 20000 /*us*/
+
+#if defined(OSHURD)
+# include <hurd.h>
+# include <ps.h>
+#endif
+
+#if defined(OSOpenBSD) || defined(OSFreeBSD) || defined(OSNetBSD) || defined(OSDarwin)
+#include <sys/param.h>
+#include <sys/user.h>
+#include <sys/proc.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <kvm.h>
+#include <limits.h>
+#endif
+
+#if defined(OShpux)
+#include <sys/param.h>
+#include <sys/pstat.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/termios.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "headers.h"
+
+#ifdef HURD_IHASH_H
+# include <hurd/ihash.h>
+#endif
+
+static int testmode = 0;
+static int quietmode = 0;
+static int exitnodo = 1;
+static int start = 0;
+static int stop = 0;
+static int background = 0;
+static int mpidfile = 0;
+static int signal_nr = 15;
+static const char *signal_str = NULL;
+static int user_id = -1;
+static int runas_uid = -1;
+static int runas_gid = -1;
+static const char *userspec = NULL;
+static char *changeuser = NULL;
+static const char *changegroup = NULL;
+static char *changeroot = NULL;
+static const char *changedir = NULL;
+static const char *cmdname = NULL;
+static char *execname = NULL;
+static char *startas = NULL;
+static const char *pidfile = NULL;
+static char what_stop[1024];
+static const char *schedule_str = NULL;
+static const char *progname = "";
+static int nicelevel = 0;
+
+static struct stat exec_stat;
+#if defined(OSHURD)
+static struct proc_stat_list *procset;
+#endif
+
+
+struct pid_list {
+ struct pid_list *next;
+ pid_t pid;
+};
+
+static struct pid_list *found = NULL;
+static struct pid_list *killed = NULL;
+
+struct schedule_item {
+ enum { sched_timeout, sched_signal, sched_goto, sched_forever } type;
+ int value; /* seconds, signal no., or index into array */
+ /* sched_forever is only seen within parse_schedule and callees */
+};
+
+static int schedule_length;
+static struct schedule_item *schedule = NULL;
+
+static void *xmalloc(int size);
+static void push(struct pid_list **list, pid_t pid);
+static void do_help(void);
+static void parse_options(int argc, char * const *argv);
+static int pid_is_user(pid_t pid, uid_t uid);
+static int pid_is_cmd(pid_t pid, const char *name);
+static void check(pid_t pid);
+static void do_pidfile(const char *name);
+static void do_stop(int signal_nr, int quietmode,
+ int *n_killed, int *n_notkilled, int retry_nr);
+#if defined(OSLinux) || defined(OShpux)
+static int pid_is_exec(pid_t pid, const struct stat *esb);
+#endif
+
+#ifdef __GNUC__
+static void fatal(const char *format, ...)
+ NONRETURNPRINTFFORMAT(1, 2);
+static void badusage(const char *msg)
+ NONRETURNING;
+#else
+static void fatal(const char *format, ...);
+static void badusage(const char *msg);
+#endif
+
+/* This next part serves only to construct the TVCALC macro, which
+ * is used for doing arithmetic on struct timeval's. It works like this:
+ * TVCALC(result, expression);
+ * where result is a struct timeval (and must be an lvalue) and
+ * expression is the single expression for both components. In this
+ * expression you can use the special values TVELEM, which when fed a
+ * const struct timeval* gives you the relevant component, and
+ * TVADJUST. TVADJUST is necessary when subtracting timevals, to make
+ * it easier to renormalise. Whenver you subtract timeval elements,
+ * you must make sure that TVADJUST is added to the result of the
+ * subtraction (before any resulting multiplication or what have you).
+ * TVELEM must be linear in TVADJUST.
+ */
+typedef long tvselector(const struct timeval*);
+static long tvselector_sec(const struct timeval *tv) { return tv->tv_sec; }
+static long tvselector_usec(const struct timeval *tv) { return tv->tv_usec; }
+#define TVCALC_ELEM(result, expr, sec, adj) \
+{ \
+ const long TVADJUST = adj; \
+ long (*const TVELEM)(const struct timeval*) = tvselector_##sec; \
+ (result).tv_##sec = (expr); \
+}
+#define TVCALC(result,expr) \
+do { \
+ TVCALC_ELEM(result, expr, sec, (-1)); \
+ TVCALC_ELEM(result, expr, usec, (+1000000)); \
+ (result).tv_sec += (result).tv_usec / 1000000; \
+ (result).tv_usec %= 1000000; \
+} while(0)
+
+
+static void
+fatal(const char *format, ...)
+{
+ va_list arglist;
+
+ fprintf(stderr, "%s: ", progname);
+ va_start(arglist, format);
+ vfprintf(stderr, format, arglist);
+ va_end(arglist);
+ putc('\n', stderr);
+ exit(2);
+}
+
+
+static void *
+xmalloc(int size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (ptr)
+ return ptr;
+ fatal("malloc(%d) failed", size);
+}
+
+
+static void
+xgettimeofday(struct timeval *tv)
+{
+ if (gettimeofday(tv,0) != 0)
+ fatal("gettimeofday failed: %s", strerror(errno));
+}
+
+
+static void
+push(struct pid_list **list, pid_t pid)
+{
+ struct pid_list *p;
+
+ p = xmalloc(sizeof(*p));
+ p->next = *list;
+ p->pid = pid;
+ *list = p;
+}
+
+static void
+clear(struct pid_list **list)
+{
+ struct pid_list *here, *next;
+
+ for (here = *list; here != NULL; here = next) {
+ next = here->next;
+ free(here);
+ }
+
+ *list = NULL;
+}
+
+static void
+do_help(void)
+{
+ printf(
+"start-stop-daemon " VERSION " for Debian - small and fast C version written by\n"
+"Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, public domain.\n"
+"\n"
+"Usage:\n"
+" start-stop-daemon -S|--start options ... -- arguments ...\n"
+" start-stop-daemon -K|--stop options ...\n"
+" start-stop-daemon -H|--help\n"
+" start-stop-daemon -V|--version\n"
+"\n"
+"Options (at least one of --exec|--pidfile|--user is required):\n"
+" -x|--exec <executable> program to start/check if it is running\n"
+" -p|--pidfile <pid-file> pid file to check\n"
+" -c|--chuid <name|uid[:group|gid]>\n"
+" change to this user/group before starting process\n"
+" -u|--user <username>|<uid> stop processes owned by this user\n"
+" -g|--group <group|gid> run process as this group\n"
+" -n|--name <process-name> stop processes with this name\n"
+" -s|--signal <signal> signal to send (default TERM)\n"
+" -a|--startas <pathname> program to start (default is <executable>)\n"
+" -C|--chdir <directory> Change to <directory>(default is /)\n"
+" -N|--nicelevel <incr> add incr to the process's nice level\n"
+" -b|--background force the process to detach\n"
+" -m|--make-pidfile create the pidfile before starting\n"
+" -R|--retry <schedule> check whether processes die, and retry\n"
+" -t|--test test mode, don't do anything\n"
+" -o|--oknodo exit status 0 (not 1) if nothing done\n"
+" -q|--quiet be more quiet\n"
+" -v|--verbose be more verbose\n"
+"Retry <schedule> is <item>|/<item>/... where <item> is one of\n"
+" -<signal-num>|[-]<signal-name> send that signal\n"
+" <timeout> wait that many seconds\n"
+" forever repeat remainder forever\n"
+"or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n"
+"\n"
+"Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n"
+" 3 = trouble 2 = with --retry, processes wouldn't die\n");
+}
+
+
+static void
+badusage(const char *msg)
+{
+ if (msg)
+ fprintf(stderr, "%s: %s\n", progname, msg);
+ fprintf(stderr, "Try `%s --help' for more information.\n", progname);
+ exit(3);
+}
+
+struct sigpair {
+ const char *name;
+ int signal;
+};
+
+const struct sigpair siglist[] = {
+ { "ABRT", SIGABRT },
+ { "ALRM", SIGALRM },
+ { "FPE", SIGFPE },
+ { "HUP", SIGHUP },
+ { "ILL", SIGILL },
+ { "INT", SIGINT },
+ { "KILL", SIGKILL },
+ { "PIPE", SIGPIPE },
+ { "QUIT", SIGQUIT },
+ { "SEGV", SIGSEGV },
+ { "TERM", SIGTERM },
+ { "USR1", SIGUSR1 },
+ { "USR2", SIGUSR2 },
+ { "CHLD", SIGCHLD },
+ { "CONT", SIGCONT },
+ { "STOP", SIGSTOP },
+ { "TSTP", SIGTSTP },
+ { "TTIN", SIGTTIN },
+ { "TTOU", SIGTTOU }
+};
+
+static int parse_integer(const char *string, int *value_r) {
+ unsigned long ul;
+ char *ep;
+
+ if (!string[0])
+ return -1;
+
+ ul= strtoul(string,&ep,10);
+ if (ul > INT_MAX || *ep != '\0')
+ return -1;
+
+ *value_r= ul;
+ return 0;
+}
+
+static int parse_signal(const char *signal_str, int *signal_nr)
+{
+ unsigned int i;
+
+ if (parse_integer(signal_str, signal_nr) == 0)
+ return 0;
+
+ for (i = 0; i < sizeof (siglist) / sizeof (siglist[0]); i++) {
+ if (strcmp (signal_str, siglist[i].name) == 0) {
+ *signal_nr = siglist[i].signal;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void
+parse_schedule_item(const char *string, struct schedule_item *item) {
+ const char *after_hyph;
+
+ if (!strcmp(string,"forever")) {
+ item->type = sched_forever;
+ } else if (isdigit(string[0])) {
+ item->type = sched_timeout;
+ if (parse_integer(string, &item->value) != 0)
+ badusage("invalid timeout value in schedule");
+ } else if ((after_hyph = string + (string[0] == '-')) &&
+ parse_signal(after_hyph, &item->value) == 0) {
+ item->type = sched_signal;
+ } else {
+ badusage("invalid schedule item (must be [-]<signal-name>, "
+ "-<signal-number>, <timeout> or `forever'");
+ }
+}
+
+static void
+parse_schedule(const char *schedule_str) {
+ char item_buf[20];
+ const char *slash;
+ int count, repeatat;
+ ptrdiff_t str_len;
+
+ count = 0;
+ for (slash = schedule_str; *slash; slash++)
+ if (*slash == '/')
+ count++;
+
+ schedule_length = (count == 0) ? 4 : count+1;
+ schedule = xmalloc(sizeof(*schedule) * schedule_length);
+
+ if (count == 0) {
+ schedule[0].type = sched_signal;
+ schedule[0].value = signal_nr;
+ parse_schedule_item(schedule_str, &schedule[1]);
+ if (schedule[1].type != sched_timeout) {
+ badusage ("--retry takes timeout, or schedule list"
+ " of at least two items");
+ }
+ schedule[2].type = sched_signal;
+ schedule[2].value = SIGKILL;
+ schedule[3]= schedule[1];
+ } else {
+ count = 0;
+ repeatat = -1;
+ while (schedule_str != NULL) {
+ slash = strchr(schedule_str,'/');
+ str_len = slash ? slash - schedule_str : strlen(schedule_str);
+ if (str_len >= (ptrdiff_t)sizeof(item_buf))
+ badusage("invalid schedule item: far too long"
+ " (you must delimit items with slashes)");
+ memcpy(item_buf, schedule_str, str_len);
+ item_buf[str_len] = 0;
+ schedule_str = slash ? slash+1 : NULL;
+
+ parse_schedule_item(item_buf, &schedule[count]);
+ if (schedule[count].type == sched_forever) {
+ if (repeatat >= 0)
+ badusage("invalid schedule: `forever'"
+ " appears more than once");
+ repeatat = count;
+ continue;
+ }
+ count++;
+ }
+ if (repeatat >= 0) {
+ schedule[count].type = sched_goto;
+ schedule[count].value = repeatat;
+ count++;
+ }
+ assert(count == schedule_length);
+ }
+}
+
+static void
+parse_options(int argc, char * const *argv)
+{
+ static struct option longopts[] = {
+ { "help", 0, NULL, 'H'},
+ { "stop", 0, NULL, 'K'},
+ { "start", 0, NULL, 'S'},
+ { "version", 0, NULL, 'V'},
+ { "startas", 1, NULL, 'a'},
+ { "name", 1, NULL, 'n'},
+ { "oknodo", 0, NULL, 'o'},
+ { "pidfile", 1, NULL, 'p'},
+ { "quiet", 0, NULL, 'q'},
+ { "signal", 1, NULL, 's'},
+ { "test", 0, NULL, 't'},
+ { "user", 1, NULL, 'u'},
+ { "group", 1, NULL, 'g'},
+ { "chroot", 1, NULL, 'r'},
+ { "verbose", 0, NULL, 'v'},
+ { "exec", 1, NULL, 'x'},
+ { "chuid", 1, NULL, 'c'},
+ { "nicelevel", 1, NULL, 'N'},
+ { "background", 0, NULL, 'b'},
+ { "make-pidfile", 0, NULL, 'm'},
+ { "retry", 1, NULL, 'R'},
+ { "chdir", 1, NULL, 'd'},
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+
+ for (;;) {
+ c = getopt_long(argc, argv, "HKSV:a:n:op:qr:s:tu:vx:c:N:bmR:g:d:",
+ longopts, (int *) 0);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'H': /* --help */
+ do_help();
+ exit(0);
+ case 'K': /* --stop */
+ stop = 1;
+ break;
+ case 'S': /* --start */
+ start = 1;
+ break;
+ case 'V': /* --version */
+ printf("start-stop-daemon " VERSION "\n");
+ exit(0);
+ case 'a': /* --startas <pathname> */
+ startas = optarg;
+ break;
+ case 'n': /* --name <process-name> */
+ cmdname = optarg;
+ break;
+ case 'o': /* --oknodo */
+ exitnodo = 0;
+ break;
+ case 'p': /* --pidfile <pid-file> */
+ pidfile = optarg;
+ break;
+ case 'q': /* --quiet */
+ quietmode = 1;
+ break;
+ case 's': /* --signal <signal> */
+ signal_str = optarg;
+ break;
+ case 't': /* --test */
+ testmode = 1;
+ break;
+ case 'u': /* --user <username>|<uid> */
+ userspec = optarg;
+ break;
+ case 'v': /* --verbose */
+ quietmode = -1;
+ break;
+ case 'x': /* --exec <executable> */
+ execname = optarg;
+ break;
+ case 'c': /* --chuid <username>|<uid> */
+ /* we copy the string just in case we need the
+ * argument later. */
+ changeuser = strdup(optarg);
+ changeuser = strtok(changeuser, ":");
+ changegroup = strtok(NULL, ":");
+ break;
+ case 'g': /* --group <group>|<gid> */
+ changegroup = optarg;
+ break;
+ case 'r': /* --chroot /new/root */
+ changeroot = optarg;
+ break;
+ case 'N': /* --nice */
+ nicelevel = atoi(optarg);
+ break;
+ case 'b': /* --background */
+ background = 1;
+ break;
+ case 'm': /* --make-pidfile */
+ mpidfile = 1;
+ break;
+ case 'R': /* --retry <schedule>|<timeout> */
+ schedule_str = optarg;
+ break;
+ case 'd': /* --chdir /new/dir */
+ changedir = optarg;
+ break;
+ default:
+ badusage(NULL); /* message printed by getopt */
+ }
+ }
+
+ if (signal_str != NULL) {
+ if (parse_signal (signal_str, &signal_nr) != 0)
+ badusage("signal value must be numeric or name"
+ " of signal (KILL, INT, ...)");
+ }
+
+ if (schedule_str != NULL) {
+ parse_schedule(schedule_str);
+ }
+
+ if (start == stop)
+ badusage("need one of --start or --stop");
+
+ if (!execname && !pidfile && !userspec && !cmdname)
+ badusage("need at least one of --exec, --pidfile, --user or --name");
+
+ if (!startas)
+ startas = execname;
+
+ if (start && !startas)
+ badusage("--start needs --exec or --startas");
+
+ if (mpidfile && pidfile == NULL)
+ badusage("--make-pidfile is only relevant with --pidfile");
+
+ if (background && !start)
+ badusage("--background is only relevant with --start");
+
+}
+
+#if defined(OSLinux)
+static int
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct stat sb;
+ char buf[32];
+
+ sprintf(buf, "/proc/%d/exe", pid);
+ if (stat(buf, &sb) != 0)
+ return 0;
+ return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+}
+
+
+static int
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct stat sb;
+ char buf[32];
+
+ sprintf(buf, "/proc/%d", pid);
+ if (stat(buf, &sb) != 0)
+ return 0;
+ return (sb.st_uid == uid);
+}
+
+
+static int
+pid_is_cmd(pid_t pid, const char *name)
+{
+ char buf[32];
+ FILE *f;
+ int c;
+
+ sprintf(buf, "/proc/%d/stat", pid);
+ f = fopen(buf, "r");
+ if (!f)
+ return 0;
+ while ((c = getc(f)) != EOF && c != '(')
+ ;
+ if (c != '(') {
+ fclose(f);
+ return 0;
+ }
+ /* this hopefully handles command names containing ')' */
+ while ((c = getc(f)) != EOF && c == *name)
+ name++;
+ fclose(f);
+ return (c == ')' && *name == '\0');
+}
+#endif /* OSLinux */
+
+
+#if defined(OSHURD)
+static int
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct stat sb;
+ char buf[32];
+ struct proc_stat *pstat;
+
+ sprintf(buf, "/proc/%d", pid);
+ if (stat(buf, &sb) != 0)
+ return 0;
+ return (sb.st_uid == uid);
+ pstat = proc_stat_list_pid_proc_stat (procset, pid);
+ if (pstat == NULL)
+ fatal ("Error getting process information: NULL proc_stat struct");
+ proc_stat_set_flags (pstat, PSTAT_PID | PSTAT_OWNER_UID);
+ return (pstat->owner_uid == uid);
+}
+
+static int
+pid_is_cmd(pid_t pid, const char *name)
+{
+ struct proc_stat *pstat;
+ pstat = proc_stat_list_pid_proc_stat (procset, pid);
+ if (pstat == NULL)
+ fatal ("Error getting process information: NULL proc_stat struct");
+ proc_stat_set_flags (pstat, PSTAT_PID | PSTAT_ARGS);
+ return (!strcmp (name, pstat->args));
+}
+#endif /* OSHURD */
+
+
+static int
+pid_is_running(pid_t pid)
+{
+ struct stat sb;
+ char buf[32];
+
+ sprintf(buf, "/proc/%d", pid);
+ if (stat(buf, &sb) != 0) {
+ if (errno!=ENOENT)
+ fatal("Error stating %s: %s", buf, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+check(pid_t pid)
+{
+#if defined(OSLinux) || defined(OShpux)
+ if (execname && !pid_is_exec(pid, &exec_stat))
+ return;
+#elif defined(OSHURD) || defined(OSFreeBSD) || defined(OSNetBSD) || defined(OSDarwin)
+ /* I will try this to see if it works */
+ if (execname && !pid_is_cmd(pid, execname))
+ return;
+#endif
+ if (userspec && !pid_is_user(pid, user_id))
+ return;
+ if (cmdname && !pid_is_cmd(pid, cmdname))
+ return;
+ if (start && !pid_is_running(pid))
+ return;
+ push(&found, pid);
+}
+
+static void
+do_pidfile(const char *name)
+{
+ FILE *f;
+ pid_t pid;
+
+ f = fopen(name, "r");
+ if (f) {
+ if (fscanf(f, "%d", &pid) == 1)
+ check(pid);
+ fclose(f);
+ } else if (errno != ENOENT)
+ fatal("open pidfile %s: %s", name, strerror(errno));
+
+}
+
+/* WTA: this needs to be an autoconf check for /proc/pid existance.
+ */
+
+#if defined(OSLinux) || defined (OSsunos) || defined(OSfreebsd)
+static void
+do_procinit(void)
+{
+ DIR *procdir;
+ struct dirent *entry;
+ int foundany;
+ pid_t pid;
+
+ procdir = opendir("/proc");
+ if (!procdir)
+ fatal("opendir /proc: %s", strerror(errno));
+
+ foundany = 0;
+ while ((entry = readdir(procdir)) != NULL) {
+ if (sscanf(entry->d_name, "%d", &pid) != 1)
+ continue;
+ foundany++;
+ check(pid);
+ }
+ closedir(procdir);
+ if (!foundany)
+ fatal("nothing in /proc - not mounted?");
+}
+#endif /* OSLinux */
+
+
+#if defined(OSHURD)
+error_t
+check_all(void *ptr)
+{
+ struct proc_stat *pstat = ptr;
+
+ check(pstat->pid);
+ return 0;
+}
+
+static void
+do_procinit(void)
+{
+ struct ps_context *context;
+ error_t err;
+
+ err = ps_context_create(getproc(), &context);
+ if (err)
+ error(1, err, "ps_context_create");
+
+ err = proc_stat_list_create(context, &procset);
+ if (err)
+ error(1, err, "proc_stat_list_create");
+
+ err = proc_stat_list_add_all(procset, 0, 0);
+ if (err)
+ error(1, err, "proc_stat_list_add_all");
+
+ /* Check all pids */
+ ihash_iterate(context->procs, check_all);
+}
+#endif /* OSHURD */
+
+
+#if defined(OSOpenBSD) || defined(OSFreeBSD) || defined(OSNetBSD)
+static int
+pid_is_cmd(pid_t pid, const char *name)
+{
+ kvm_t *kd;
+ int nentries, argv_len=0;
+ struct kinfo_proc *kp;
+ char errbuf[_POSIX2_LINE_MAX], buf[_POSIX2_LINE_MAX];
+ char **pid_argv_p;
+ char *start_argv_0_p, *end_argv_0_p;
+
+
+ kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
+ if (kd == 0)
+ errx(1, "%s", errbuf);
+ if ((kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &nentries)) == 0)
+ errx(1, "%s", kvm_geterr(kd));
+ if ((pid_argv_p = kvm_getargv(kd, kp, argv_len)) == 0)
+ errx(1, "%s", kvm_geterr(kd));
+
+ start_argv_0_p = *pid_argv_p;
+ /* find and compare string */
+
+ /* find end of argv[0] then copy and cut of str there. */
+ if ((end_argv_0_p = strchr(*pid_argv_p, ' ')) == 0 )
+ /* There seems to be no space, so we have the command
+ * allready in its desired form. */
+ start_argv_0_p = *pid_argv_p;
+ else {
+ /* Tests indicate that this never happens, since
+ * kvm_getargv itselfe cuts of tailing stuff. This is
+ * not what the manpage says, however. */
+ strncpy(buf, *pid_argv_p, (end_argv_0_p - start_argv_0_p));
+ buf[(end_argv_0_p - start_argv_0_p) + 1] = '\0';
+ start_argv_0_p = buf;
+ }
+
+ if (strlen(name) != strlen(start_argv_0_p))
+ return 0;
+ return (strcmp(name, start_argv_0_p) == 0) ? 1 : 0;
+}
+
+static int
+pid_is_user(pid_t pid, uid_t uid)
+{
+ kvm_t *kd;
+ int nentries; /* Value not used */
+ uid_t proc_uid;
+ struct kinfo_proc *kp;
+ char errbuf[_POSIX2_LINE_MAX];
+
+
+ kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
+ if (kd == 0)
+ errx(1, "%s", errbuf);
+ if ((kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &nentries)) == 0)
+ errx(1, "%s", kvm_geterr(kd));
+ if (kp->ki_ruid )
+ kvm_read(kd, (u_long)&(kp->ki_ruid),
+ &proc_uid, sizeof(uid_t));
+ else
+ return 0;
+ return (proc_uid == (uid_t)uid);
+}
+
+static int
+pid_is_exec(pid_t pid, const char *name)
+{
+ kvm_t *kd;
+ int nentries;
+ struct kinfo_proc *kp;
+ char errbuf[_POSIX2_LINE_MAX], *pidexec;
+
+ kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
+ if (kd == 0)
+ errx(1, "%s", errbuf);
+ if ((kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &nentries)) == 0)
+ errx(1, "%s", kvm_geterr(kd));
+ pidexec = kp->ki_comm;
+ if (strlen(name) != strlen(pidexec))
+ return 0;
+ return (strcmp(name, pidexec) == 0) ? 1 : 0;
+}
+
+
+static void
+do_procinit(void)
+{
+ /* Nothing to do */
+}
+
+#endif /* OSOpenBSD */
+
+#if defined(OSDarwin)
+int
+pid_is_user(pid_t pid, uid_t uid)
+{
+ int mib[4];
+ size_t size;
+ struct kinfo_proc ki;
+
+ size = sizeof(ki);
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ if (sysctl(mib, 4, &ki, &size, NULL, 0) < 0)
+ errx(1, "%s", "Failure calling sysctl");
+ return (uid == ki.kp_eproc.e_pcred.p_ruid);
+}
+
+static int
+pid_is_cmd(pid_t pid, const char *name)
+{
+ int mib[4];
+ size_t size;
+ struct kinfo_proc ki;
+
+ size = sizeof(ki);
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ if (sysctl(mib, 4, &ki, &size, NULL, 0) < 0)
+ errx(1, "%s", "Failure calling sysctl");
+ return (!strncmp(name, ki.kp_proc.p_comm, MAXCOMLEN));
+}
+
+static void
+do_procinit(void)
+{
+ int mib[3];
+ size_t size;
+ int nprocs, ret, i;
+ struct kinfo_proc *procs = NULL, *newprocs;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_ALL;
+ ret = sysctl(mib, 3, NULL, &size, NULL, 0);
+ /* Allocate enough memory for entire process table */
+ do {
+ size += size / 10;
+ newprocs = realloc(procs, size);
+ if (newprocs == NULL) {
+ if (procs)
+ free(procs);
+ errx(1, "%s", "Could not reallocate memory");
+ }
+ procs = newprocs;
+ ret = sysctl(mib, 3, procs, &size, NULL, 0);
+ } while (ret >= 0 && errno == ENOMEM);
+
+ if (ret < 0)
+ errx(1, "%s", "Failure calling sysctl");
+
+ /* Verify size of proc structure */
+ if (size % sizeof(struct kinfo_proc) != 0)
+ errx(1, "%s", "proc size mismatch, userland out of sync with kernel");
+ nprocs = size / sizeof(struct kinfo_proc);
+ for (i = 0; i < nprocs; i++) {
+ check(procs[i].kp_proc.p_pid);
+ }
+}
+#endif /* OSDarwin */
+#if defined(OShpux)
+static int
+pid_is_user(pid_t pid, uid_t uid)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t) 0, (int) pid) < 0)
+ return 0;
+ return ((uid_t) pst.pst_uid == uid);
+}
+
+static int
+pid_is_cmd(pid_t pid, const char *name)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t) 0, (int) pid) < 0)
+ return 0;
+ return (strcmp(pst.pst_ucomm, name) == 0);
+}
+
+static int
+pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct pst_status pst;
+
+ if (pstat_getproc(&pst, sizeof(pst), (size_t) 0, (int) pid) < 0)
+ return 0;
+ return ((dev_t) pst.pst_text.psf_fsid.psfs_id == esb->st_dev
+ && (ino_t) pst.pst_text.psf_fileid == esb->st_ino);
+}
+
+static void
+do_procinit(void)
+{
+ struct pst_status pst[10];
+ int i, count;
+ int idx = 0;
+
+ while ((count = pstat_getproc(pst, sizeof(pst[0]), 10, idx)) > 0) {
+ for (i = 0; i < count; i++)
+ check(pst[i].pst_pid);
+ idx = pst[count - 1].pst_idx + 1;
+ }
+}
+#endif /* OShpux */
+
+
+static void
+do_findprocs(void)
+{
+ clear(&found);
+
+ if (pidfile)
+ do_pidfile(pidfile);
+ else
+ do_procinit();
+}
+
+/* return 1 on failure */
+static void
+do_stop(int signal_nr, int quietmode, int *n_killed, int *n_notkilled, int retry_nr)
+{
+ struct pid_list *p;
+
+ do_findprocs();
+
+ *n_killed = 0;
+ *n_notkilled = 0;
+
+ if (!found)
+ return;
+
+ clear(&killed);
+
+ for (p = found; p; p = p->next) {
+ if (testmode) {
+ printf("Would send signal %d to %d.\n",
+ signal_nr, p->pid);
+ (*n_killed)++;
+ } else if (kill(p->pid, signal_nr) == 0) {
+ push(&killed, p->pid);
+ (*n_killed)++;
+ } else {
+ printf("%s: warning: failed to kill %d: %s\n",
+ progname, p->pid, strerror(errno));
+ (*n_notkilled)++;
+ }
+ }
+ if (quietmode < 0 && killed) {
+ printf("Stopped %s (pid", what_stop);
+ for (p = killed; p; p = p->next)
+ printf(" %d", p->pid);
+ putchar(')');
+ if (retry_nr > 0)
+ printf(", retry #%d", retry_nr);
+ printf(".\n");
+ }
+}
+
+
+static void
+set_what_stop(const char *str)
+{
+ strncpy(what_stop, str, sizeof(what_stop));
+ what_stop[sizeof(what_stop)-1] = '\0';
+}
+
+static int
+run_stop_schedule(void)
+{
+ int r, position, n_killed, n_notkilled, value, ratio, anykilled, retry_nr;
+ struct timeval stopat, before, after, interval, maxinterval;
+
+ if (testmode) {
+ if (schedule != NULL) {
+ printf("Ignoring --retry in test mode\n");
+ schedule = NULL;
+ }
+ }
+
+ if (cmdname)
+ set_what_stop(cmdname);
+ else if (execname)
+ set_what_stop(execname);
+ else if (pidfile)
+ sprintf(what_stop, "process in pidfile `%.200s'", pidfile);
+ else if (userspec)
+ sprintf(what_stop, "process(es) owned by `%.200s'", userspec);
+ else
+ fatal("internal error, please report");
+
+ anykilled = 0;
+ retry_nr = 0;
+
+ if (schedule == NULL) {
+ do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0);
+ if (n_notkilled > 0 && quietmode <= 0)
+ printf("%d pids were not killed\n", n_notkilled);
+ if (n_killed)
+ anykilled = 1;
+ goto x_finished;
+ }
+
+ for (position = 0; position < schedule_length; ) {
+ value= schedule[position].value;
+ n_notkilled = 0;
+
+ switch (schedule[position].type) {
+
+ case sched_goto:
+ position = value;
+ continue;
+
+ case sched_signal:
+ do_stop(value, quietmode, &n_killed, &n_notkilled, retry_nr++);
+ if (!n_killed)
+ goto x_finished;
+ else
+ anykilled = 1;
+ goto next_item;
+
+ case sched_timeout:
+ /* We want to keep polling for the processes, to see if they've exited,
+ * or until the timeout expires.
+ *
+ * This is a somewhat complicated algorithm to try to ensure that we
+ * notice reasonably quickly when all the processes have exited, but
+ * don't spend too much CPU time polling. In particular, on a fast
+ * machine with quick-exiting daemons we don't want to delay system
+ * shutdown too much, whereas on a slow one, or where processes are
+ * taking some time to exit, we want to increase the polling
+ * interval.
+ *
+ * The algorithm is as follows: we measure the elapsed time it takes
+ * to do one poll(), and wait a multiple of this time for the next
+ * poll. However, if that would put us past the end of the timeout
+ * period we wait only as long as the timeout period, but in any case
+ * we always wait at least MIN_POLL_INTERVAL (20ms). The multiple
+ * (`ratio') starts out as 2, and increases by 1 for each poll to a
+ * maximum of 10; so we use up to between 30% and 10% of the
+ * machine's resources (assuming a few reasonable things about system
+ * performance).
+ */
+ xgettimeofday(&stopat);
+ stopat.tv_sec += value;
+ ratio = 1;
+ for (;;) {
+ xgettimeofday(&before);
+ if (timercmp(&before,&stopat,>))
+ goto next_item;
+
+ do_stop(0, 1, &n_killed, &n_notkilled, 0);
+ if (!n_killed)
+ goto x_finished;
+
+ xgettimeofday(&after);
+
+ if (!timercmp(&after,&stopat,<))
+ goto next_item;
+
+ if (ratio < 10)
+ ratio++;
+
+ TVCALC(interval, ratio * (TVELEM(&after) - TVELEM(&before) + TVADJUST));
+ TVCALC(maxinterval, TVELEM(&stopat) - TVELEM(&after) + TVADJUST);
+
+ if (timercmp(&interval,&maxinterval,>))
+ interval = maxinterval;
+
+ if (interval.tv_sec == 0 &&
+ interval.tv_usec <= MIN_POLL_INTERVAL)
+ interval.tv_usec = MIN_POLL_INTERVAL;
+
+ r = select(0,0,0,0,&interval);
+ if (r < 0 && errno != EINTR)
+ fatal("select() failed for pause: %s",
+ strerror(errno));
+ }
+
+ default:
+ assert(!"schedule[].type value must be valid");
+
+ }
+
+ next_item:
+ position++;
+ }
+
+ if (quietmode <= 0)
+ printf("Program %s, %d process(es), refused to die.\n",
+ what_stop, n_killed);
+
+ return 2;
+
+x_finished:
+ if (!anykilled) {
+ if (quietmode <= 0)
+ printf("No %s found running; none killed.\n", what_stop);
+ return exitnodo;
+ } else {
+ return 0;
+ }
+}
+
+
+int main(int argc, char **argv) NONRETURNING;
+int
+main(int argc, char **argv)
+{
+ int devnull_fd = -1;
+#ifdef HAVE_TIOCNOTTY
+ int tty_fd = -1;
+#endif
+ progname = argv[0];
+
+ parse_options(argc, argv);
+ argc -= optind;
+ argv += optind;
+
+ if (execname && stat(execname, &exec_stat))
+ fatal("stat %s: %s", execname, strerror(errno));
+
+ if (userspec && sscanf(userspec, "%d", &user_id) != 1) {
+ struct passwd *pw;
+
+ pw = getpwnam(userspec);
+ if (!pw)
+ fatal("user `%s' not found\n", userspec);
+
+ user_id = pw->pw_uid;
+ }
+
+ if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) {
+ struct group *gr = getgrnam(changegroup);
+ if (!gr)
+ fatal("group `%s' not found\n", changegroup);
+ runas_gid = gr->gr_gid;
+ }
+ if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) {
+ struct passwd *pw = getpwnam(changeuser);
+ if (!pw)
+ fatal("user `%s' not found\n", changeuser);
+ runas_uid = pw->pw_uid;
+ if (changegroup == NULL) { /* pass the default group of this user */
+ changegroup = ""; /* just empty */
+ runas_gid = pw->pw_gid;
+ }
+ }
+
+ if (stop) {
+ int i = run_stop_schedule();
+ exit(i);
+ }
+
+ do_findprocs();
+
+ if (found) {
+ if (quietmode <= 0)
+ printf("%s already running.\n", execname ? execname : "process");
+ exit(exitnodo);
+ }
+ if (testmode) {
+ printf("Would start %s ", startas);
+ while (argc-- > 0)
+ printf("%s ", *argv++);
+ if (changeuser != NULL) {
+ printf(" (as user %s[%d]", changeuser, runas_uid);
+ if (changegroup != NULL)
+ printf(", and group %s[%d])", changegroup, runas_gid);
+ else
+ printf(")");
+ }
+ if (changeroot != NULL)
+ printf(" in directory %s", changeroot);
+ if (nicelevel)
+ printf(", and add %i to the priority", nicelevel);
+ printf(".\n");
+ exit(0);
+ }
+ if (quietmode < 0)
+ printf("Starting %s...\n", startas);
+ *--argv = startas;
+ if (background) { /* ok, we need to detach this process */
+ int i;
+ if (quietmode < 0)
+ printf("Detatching to start %s...", startas);
+ i = fork();
+ if (i<0) {
+ fatal("Unable to fork.\n");
+ }
+ if (i) { /* parent */
+ if (quietmode < 0)
+ printf("done.\n");
+ exit(0);
+ }
+ /* child continues here */
+
+#ifdef HAVE_TIOCNOTTY
+ tty_fd=open("/dev/tty", O_RDWR);
+#endif
+ devnull_fd=open("/dev/null", O_RDWR);
+ }
+ if (nicelevel) {
+ errno=0;
+ if ((nice(nicelevel)==-1) && (errno!=0))
+ fatal("Unable to alter nice level by %i: %s", nicelevel,
+ strerror(errno));
+ }
+ if (mpidfile && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */
+ FILE *pidf = fopen(pidfile, "w");
+ pid_t pidt = getpid();
+ if (pidf == NULL)
+ fatal("Unable to open pidfile `%s' for writing: %s", pidfile,
+ strerror(errno));
+ fprintf(pidf, "%d\n", pidt);
+ fclose(pidf);
+ }
+ if (changeroot != NULL) {
+ if (chdir(changeroot) < 0)
+ fatal("Unable to chdir() to %s", changeroot);
+ if (chroot(changeroot) < 0)
+ fatal("Unable to chroot() to %s", changeroot);
+ }
+ if (changedir != NULL && chdir(changedir) < 0)
+ fatal("Unable to chdir() to %s", changedir);
+ if (changeuser != NULL) {
+ if (setgid(runas_gid))
+ fatal("Unable to set gid to %d", runas_gid);
+ if (initgroups(changeuser, runas_gid))
+ fatal("Unable to set initgroups() with gid %d", runas_gid);
+ if (setuid(runas_uid))
+ fatal("Unable to set uid to %s", changeuser);
+ }
+ if (background) { /* continue background setup */
+ int i;
+#ifdef HAVE_TIOCNOTTY
+ /* change tty */
+ ioctl(tty_fd, TIOCNOTTY, 0);
+ close(tty_fd);
+#endif
+ umask(022); /* set a default for dumb programs */
+ dup2(devnull_fd,0); /* stdin */
+ dup2(devnull_fd,1); /* stdout */
+ dup2(devnull_fd,2); /* stderr */
+#if defined(OShpux)
+ /* now close all extra fds */
+ for (i=sysconf(_SC_OPEN_MAX)-1; i>=3; --i) close(i);
+#else
+ /* now close all extra fds */
+ for (i=getdtablesize()-1; i>=3; --i) close(i);
+#endif
+
+ /* create a new session */
+#ifdef HAVE_SETSID
+ setsid();
+#else
+ setpgid(0,0);
+#endif
+ }
+ execv(startas, argv);
+ fatal("Unable to start %s: %s", startas, strerror(errno));
+}
+