From 15d9155808de928fa26bfeaaecfee7210afde0d3 Mon Sep 17 00:00:00 2001 From: Benedikt Boehm Date: Sat, 3 Sep 2005 16:10:27 +0000 Subject: import initial baselayout sources (1.12.0_pre8) svn path=/baselayout-vserver/trunk/; revision=3 --- src/.cvsignore | 5 + src/Makefile | 49 ++ src/awk/cachedepends.awk | 210 +++++++ src/awk/functions.awk | 156 +++++ src/awk/gendepends.awk | 562 ++++++++++++++++++ src/awk/genenviron.awk | 179 ++++++ src/consoletype.c | 38 ++ src/core/.cvsignore | 3 + src/core/ChangeLog | 163 ++++++ src/core/Makefile | 81 +++ src/core/README | 15 + src/core/debug.h | 85 +++ src/core/depend.c | 532 ++++++++++++++++++ src/core/depend.h | 75 +++ src/core/depscan.c | 297 ++++++++++ src/core/list.h | 446 +++++++++++++++ src/core/misc.c | 649 +++++++++++++++++++++ src/core/misc.h | 288 ++++++++++ src/core/parse.c | 1033 ++++++++++++++++++++++++++++++++++ src/core/parse.h | 110 ++++ src/core/simple-regex.c | 854 ++++++++++++++++++++++++++++ src/core/simple-regex.h | 86 +++ src/core/test-regex.c | 80 +++ src/env_whitelist | 26 + src/filefuncs/Makefile | 17 + src/filefuncs/filefuncs.c | 486 ++++++++++++++++ src/headers.h | 26 + src/runscript.c | 250 +++++++++ src/start-stop-daemon.c | 1375 +++++++++++++++++++++++++++++++++++++++++++++ 29 files changed, 8176 insertions(+) create mode 100644 src/.cvsignore create mode 100644 src/Makefile create mode 100644 src/awk/cachedepends.awk create mode 100644 src/awk/functions.awk create mode 100644 src/awk/gendepends.awk create mode 100644 src/awk/genenviron.awk create mode 100644 src/consoletype.c create mode 100644 src/core/.cvsignore create mode 100644 src/core/ChangeLog create mode 100644 src/core/Makefile create mode 100644 src/core/README create mode 100644 src/core/debug.h create mode 100644 src/core/depend.c create mode 100644 src/core/depend.h create mode 100644 src/core/depscan.c create mode 100644 src/core/list.h create mode 100644 src/core/misc.c create mode 100644 src/core/misc.h create mode 100644 src/core/parse.c create mode 100644 src/core/parse.h create mode 100644 src/core/simple-regex.c create mode 100644 src/core/simple-regex.h create mode 100644 src/core/test-regex.c create mode 100644 src/env_whitelist create mode 100644 src/filefuncs/Makefile create mode 100644 src/filefuncs/filefuncs.c create mode 100644 src/headers.h create mode 100644 src/runscript.c create mode 100644 src/start-stop-daemon.c (limited to 'src') 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_() function to resolve our + # rc_ 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 +#include +#include +#include +#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 + + * depend.c + * depend.h + * parse.c + * parse.h: Remove the "parallel" stuff, as we do not use it anymore. + +15 Apr 2005 Martin Schlemmer + + * parse.c: Do not source rc.conf for every script - once is enough. + +14 Apr 2005 Martin Schlemmer + + * 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 + + * 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 + + * Makefile: Also remove the tests in the clean target. + +11 Mar 2005 Martin Schlemmer + + * test-regex.c: Add a few strings and patterns. Enable tests to + specify if they should fail or pass. + +10 Mar 2005 Martin Schlemmer + + * 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 + + * misc.c: Fix memory leak in mktree(). + +18 Feb 2005 Martin Schlemmer + + * 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 : + + * misc.c: Fix overrun in strndup(), thanks to report from + Ned Ludd . + + * debug.h + * misc.h + * simple-regex.c: Print debug/errors to stderr, patch from + Ned Ludd . + + * debug.h: Replace invalid EXIT_FAILSTATUS with EXIT_FAILURE. + + * parse.c: Modify parse_print_body() to be more ash friendly. + Suggestions from Ned Ludd . + + * 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 : + + * 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 +# +# +# 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 + 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 + * + * + * 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 + * + * + * 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 +#include +#include +#include +#include + +#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 + * + * + * 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 +#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 + * + * + * 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 +#ifndef __KLIBC__ +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#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 +/** + * 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 +static inline void prefetch(const void *x) {;} + +//#include + +/* + * 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 + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 + * + * + * 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 +#include + +/* 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 + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * + * 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 +#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 + * + * + * 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 +#include +#include +#include + +#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 + * + * + * 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 + * + * + * 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 +#include +#include +#include + +#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 , Nov 2002 + * $Header$ + * + * Extended with: do_symlink() + * do_unlink() + * do_mkdir() + * do_rmdir() + * + * for use in the Gentoo rcscripts + * + */ + +#include "awk.h" + +#include +#include + +/* 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 +#endif +#ifdef HAVE_SYS_SYSMACROS_H +# include +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 , + * public domain. Based conceptually on start-stop-daemon.pl, by Ian + * Jackson . May be used and distributed + * freely for any purpose. Changes by Christian Schwarz + * , to make output conform to the Debian + * Console Message Standard, also placed in public domain. Minor + * changes by Klee Dienes , also placed in the Public + * Domain. + * + * Changes by Ben Collins , added --chuid, --background + * and --make-pidfile options, placed in public domain aswell. + * + * Port to OpenBSD by Sontri Tomo Huynh + * and Andreas Schuldei + * + * Changes by Ian Jackson: added --retry (and associated rearrangements). + * + * Modified for Gentoo rc-scripts by Donny Davies : + * I removed the BSD/Hurd/OtherOS stuff, added #include + * and stuck in a #define VERSION "1.9.18". Now it compiles without + * the whole automake/config.h dance. + * + * Updated by Aron Griffis : + * 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 : + * Add support for Darwin, additional patches from opendarwin.org + * fix for Gentoo bug 72145 from eldad@gentoo.org + */ + +#define VERSION "1.10.20" +#include + +#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 +# include +#endif + +#if defined(OSOpenBSD) || defined(OSFreeBSD) || defined(OSNetBSD) || defined(OSDarwin) +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#endif + +#if defined(OShpux) +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "headers.h" + +#ifdef HURD_IHASH_H +# include +#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 , 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 program to start/check if it is running\n" +" -p|--pidfile pid file to check\n" +" -c|--chuid \n" +" change to this user/group before starting process\n" +" -u|--user | stop processes owned by this user\n" +" -g|--group run process as this group\n" +" -n|--name stop processes with this name\n" +" -s|--signal signal to send (default TERM)\n" +" -a|--startas program to start (default is )\n" +" -C|--chdir Change to (default is /)\n" +" -N|--nicelevel 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 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 is |//... where is one of\n" +" -|[-] send that signal\n" +" wait that many seconds\n" +" forever repeat remainder forever\n" +"or may be just , meaning //KILL/\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 [-], " + "-, 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 */ + startas = optarg; + break; + case 'n': /* --name */ + cmdname = optarg; + break; + case 'o': /* --oknodo */ + exitnodo = 0; + break; + case 'p': /* --pidfile */ + pidfile = optarg; + break; + case 'q': /* --quiet */ + quietmode = 1; + break; + case 's': /* --signal */ + signal_str = optarg; + break; + case 't': /* --test */ + testmode = 1; + break; + case 'u': /* --user | */ + userspec = optarg; + break; + case 'v': /* --verbose */ + quietmode = -1; + break; + case 'x': /* --exec */ + execname = optarg; + break; + case 'c': /* --chuid | */ + /* 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 | */ + 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_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)); +} + -- cgit v1.2.3-65-gdbad