How to control and setup service?

Devin Teske dteske at FreeBSD.org
Thu Aug 27 21:21:44 UTC 2015


> On Aug 27, 2015, at 8:00 AM, Allan Jude <allanjude at freebsd.org> wrote:
> 
> sysrc automatically detects values from rc.conf.d directories, and can
> edit them if you ask it to. It just defaults to writing your changes to
> /etc/rc.conf
> 

Only if you use the “-f file” argument to point sysrc at said file(s).

Worth mentioning is this is in the sysrc(8) manual:

LIMITATIONS
     The sysrc utility presently does not support the `rc.conf.d' collection
     of system configuration files (which requires a service name to be known
     during execution).

     This will be corrected by a future enhancement.

So while below Allan shows us how to use `-f file’ to support rc.conf.d file(s):

> My puppet scripts use:
> sysrc -f /etc/rc.conf.d/varnish varnishd_identity="PDX1-01"
> 
> 

The OP was interested in an enhancement:

sysrc -s varnish varnishd_identity=“PDX1-01”

When given `-s service_name’, sysrc would do the following:

1. For information purposes, first make sure service_name exists in any of:
a. /etc/rc.d
b. $local_startup directories — default: /usr/local/etc/rc.d

2. If the service does not exist, issue a warning to stderr but proceed anyway
NB: If we prevent moving ahead, it would be impossible to configure a setting when service is not installed

3. If service does exist, add the following paths to list of files checked (in addition to $rc_conf_files)
i. /etc/rc.conf.d/$_name
ii. ${local_startup%/rc.d}/rc.conf.d/$_name — default /usr/local/etc/rc.conf.d/$_name

You can better simulate this effect without the `-s service_name’ enhancement,
using the currently available `-f file’ argument (more pedantic than Allan Jude’s
approach from above):

sysrc -f "/etc/rc.conf.d/$_name $( sysrc -n local_startup%/rc.d )/rc.conf.d/$_name" vimage_enable

Where $_name is the service_name.
A `-s service_name’ argument to sysrc would make this easier.

ASIDE: While I was diving into this, I discovered a code typo in /etc/rc.subr
NB: The typo has gone unnoticed because it has no effect on outcome

=== BEGIN DIFF ===
--- rc.subr.orig	2015-08-27 12:56:24.445475772 -0700
+++ rc.subr	2015-08-27 12:56:33.980474637 -0700
@@ -1333,7 +1333,7 @@ load_rc_config()
 		_rc_conf_loaded=true
 	fi
 
-	for _d in /etc ${local_startup%*/rc.d}; do
+	for _d in /etc ${local_startup%/rc.d}; do
 		if [ -f ${_d}/rc.conf.d/"$_name" ]; then
 			debug "Sourcing ${_d}/rc.conf.d/$_name"
 			. ${_d}/rc.conf.d/"$_name”
=== END DIFF ===

FURTHER ASIDE: Why is the code in /etc/rc.subr treating
$local_startup as though it’s a single item? There are many
locations in code that consider $local_startup to be a white-
space separated list of directories where rc.d scripts can live?
I think the above code should perhaps be changed to:

=== BEGIN DIFF ===
--- rc.subr.orig	2015-08-27 12:56:24.445475772 -0700
+++ rc.subr	2015-08-27 13:53:30.976240109 -0700
@@ -1333,7 +1333,8 @@ load_rc_config()
 		_rc_conf_loaded=true
 	fi
 
-	for _d in /etc ${local_startup%*/rc.d}; do
+	for _d in /etc/rc.d $local_startup; do
+		_d="${_d%/rc.d}"
 		if [ -f ${_d}/rc.conf.d/"$_name" ]; then
 			debug "Sourcing ${_d}/rc.conf.d/$_name"
 			. ${_d}/rc.conf.d/"$_name"
=== END DIFF ===


>> 
>> As a user I would love to have a cool tool to control and configure
>> services in way like OpenBSD's rcctl(8) does
>> http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/rcctl.8?query=rcctl.
>> # cooltool set mysql-server status on
>> That's all. It would take care of those things like getting right
>> $rcvar name, choose right rc config file to edit (remember, we have a
>> lot of places to check, see above) and enable service, etc.
>> This $cooltool can be an extended version of service(8) tool.
>> More difficult example:
>> # cooltool set flow_capture status on  flags "-e 2200 -n 23 -N 0 -V 5"
>> port "8787" datadir "/storage/flows/all_routers"
>> would enable flow_capture service and set other stuff using right $rcvar.
>> # cooltool get flow_capture
>> would print all of the configured stuff for $service
>> # cooltool get flow_capture status
>> would print only a status YES/NO (don't forget about exit code ;) )
>> and etc.
> 
> sysrc can do that, it will search all of the directories and find the
> final answer.
> 
> The only thing it doesn't do is the translation between the 'service
> name' (mysql-server) and the 'rcvar prefix' (mysql_),

The rcvar “prefix" is not actually what you’re after.
In load_rc_config() of /etc/rc.subr we can see that it uses $_name
to append to /etc/rc.conf.d/ and /usr/local/etc/rc.conf.d/

What exactly is “$_name” you ask? Good question…

Almost 100% of the time, it’s whatever “name=“ in the rc.d script.
This is not guaranteed to be ${0##*/} (the rc.d script name).
However, this just happens to be because almost 100% of the
time, rc.d scripts do the following:

	load_rc_config "$name"

So for sysrc to know the proper name of the thing that /etc/rc.subr
will source from rc.conf.d collection of directories, we need to know
what name= in the rc.d script.

This seems to be the simplest approach:

=== BEGIN SCRIPT: sconfigs.sh ===
#!/bin/sh
sname="$1" sconfigs=
_name=$( service "$sname" rcvar 2> /dev/null |
	awk 'NR==1&&sub(/^# /,""){print;exit}?' 2> /dev/null )
if [ "$_name" ]; then
	sconfigs="/etc/rc.conf.d/$_name"
	local_etc=$( sysrc -qn local_startup%/rc.d 2> /dev/null )
	[ "$local_etc" ] &&
		sconfigs="$sconfigs $local_etc/rc.d/rc.conf.d/$_name"
	sconfigs="${sconfigs# }"
fi
echo "sconfigs=[$sconfigs]"
=== END SCRIPT: sconfigs.sh ===

Examples:

$ ./sconfigs.sh
sconfigs=[]
$ ./sconfigs.sh zfs
sconfigs=[/etc/rc.conf.d/zfs /usr/local/etc/rc.d/rc.conf.d/zfs]
$ ./sconfigs.sh jail
sconfigs=[/etc/rc.conf.d/jail /usr/local/etc/rc.d/rc.conf.d/jail]
$ ./sconfigs.sh nosuchservice
sconfigs=[]

So naturally, if the logic in sconfigs.sh yields a non-NULL
value for $sconfigs, this would become the value of `-f files’
via service name.


> which might be a
> useful addition, but might make more sense in the service command,
> because when I pass something to sysrc, I expect it to be interpreted
> literally. I guess it could be a flag for sysrc to specify the service
> instead of the rcvar.
> 
> sysrc -s mysql datadir=/var/db/mysql

Given above talking-points, that would cause:

a. sysrc makes sure that “mysql” is an actual service
b. sysrc uses “service mysql rcvar" (piped into awk) to get `name=‘ value
NB: This could potentially result in a disconnect IFF the rc.d script passes something other than “$name” to load_rc_config() (if we’re worried about this, I have another recipe — further below).
c. Sets `-f files’ value to "/etc/rc.conf.d/mysql /usr/local/etc/rc.conf.d/mysql”

ASIDE: The only edge-case would be, for example...

=== BEGIN FILE: /etc/rc.d/fooserv ===
#!/bin/sh
. /etc/rc.subr
name=foooooooooooooOOOooo
load_rc_config foooooooooo
run_rc_command "$@“
=== END FILE: /etc/rc.d/fooserv ===

Wherein we get the following from “service … rcvar”:

$ service fooserv rcvar | awk NR==1
# foooooooooooooOOOooo

The above command returns the “name=“ value but the script doesn’t
actually pass $name to load_rc_config() and thus what /etc/rc.subr
would check for in this case is /etc/rc.conf.d/foooooooooo and
/usr/local/etc/rc.conf.d/foooooooooo (versus the camel-case $name).

This edge-case exists for a number of services in the base:

$ awk '$1 ~ /^name=/ { name = $0; fn = FILENAME } $1 == "load_rc_config" && $2 !~ /\$(name|{name})/{ print "--"; if (fn == FILENAME) print fn ":" name; print FILENAME ":" $0 }' /etc/rc.d/*
--
/etc/rc.d/dhclient:name="dhclient"
/etc/rc.d/dhclient:load_rc_config network
--
/etc/rc.d/fooserv:name=foooooooooooooOOOooo
/etc/rc.d/fooserv:load_rc_config foooooooooo
--
/etc/rc.d/initrandom:name="initrandom"
/etc/rc.d/initrandom:load_rc_config random
--
/etc/rc.d/othermta:load_rc_config 'XXX'
--
/etc/rc.d/postrandom:name="postrandom"
/etc/rc.d/postrandom:load_rc_config random
--
/etc/rc.d/swaplate:name="swaplate"
/etc/rc.d/swaplate:load_rc_config swap


NB: Ignore the /etc/rc.d/fooserv script which I created specifically to illustrate the disconnect.

For the above edge-case services in base, load_rc_config() of /etc/rc.subr will check the following:

dhclient:
	/etc/rc.conf.d/network
	/usr/local/etc/rc.conf.d/network
initrandom:
	/etc/rc.conf.d/random
	/usr/local/etc/rc.conf.d/random
othermta:
	/etc/rc.conf.d/XXX
	/usr/local/etc/rc.conf.d/XXX
postrandom:
	/etc/rc.conf.d/random
	/usr/local/etc/rc.conf.d/random
swaplate:
	/etc/rc.conf.d/swap
	/usr/local/etc/rc.conf.d/swap

Let’s ignore for a second that it seems like an obvious error to be sourcing
/usr/local/etc/rc.conf.d/ANYTHING for a base service in /etc/rc.d/
NB: I can see someone utilizing that as a form of value-add anyways

What stands out at first glance is:

+ If /etc/rc.conf.d/XXX or /usr/local/etc/rc.conf.d/XXX exists,
   it will be sourced when working on the “othermta” service

NB: My testing indicates that othermta can obtain the desired results
(of not sourcing any additional rc.conf.d files) by instead passing a
single “.” to load_c_config() — which does not allow a NULL arg1.

+ The “service <name> rcvar | awk …” approach to get “name=“
value fails for dhclient, initrandom, postrandom, and swaplate.

Oh, but it gets worse when you look deeper at the above-mentioned scripts:

$ grep -c load_rc_config /etc/rc.d/* | grep -v ':[01]$' | sed -e 's/:[[:digit:]]\{1,\}$//' | xargs grep -n load_rc_config 
/etc/rc.d/dhclient:54:load_rc_config $name
/etc/rc.d/dhclient:55:load_rc_config network
/etc/rc.d/local_unbound:32:load_rc_config $name
/etc/rc.d/local_unbound:90:load_rc_config $name

It turns out that the dhclient script will actually source all of (if any exist; in order):
1. /etc/rc.conf.d/dhclient
2. /usr/local/etc/rc.conf.d/dhclient
3. /etc/rc.conf.d/network
4. /usr/local/etc/rc.conf.d/network

I have a recipe that overcomes this edge-case that I’d like to offer
as a potential solution:

=== BEGIN FILE: sconfigs2.sh ===
#!/bin/sh
sname="$1" sconfigs=
local_startup=$( sysrc -qn local_startup )
for dir in /etc/rc.d $local_startup; do
	spath="$dir/$sname"
	[ -f "$spath" -a -x "$spath" ] || spath= continue
	break
done
if [ ! "$spath" ]; then
	echo "$sname does not exist in /etc/rc.d or the local startup"
	echo "directories ($local_startup)"
	exit 1
fi
_names=
case "$( file -b "$spath" 2> /dev/null )" in *"shell script"*)
	_names=$( exec 9<&1 1>&- 2>&-
		last_name=
		print_name() {
			local name="$1"
			[ "$name" = "$last_name" ] && return
			echo "$name" >&9
			last_name="$name"
		}
		eval "$( awk '{
			gsub(/load_rc_config /, "print_name ")
			gsub(/run_rc_command /, ": ")
			print
		}' "$spath" )"
	) ;;
esac
for _name in $_names; do
        for dir in /etc/rc.d $local_startup; do
                sconfigs="$sconfigs ${dir%/rc.d}/rc.conf.d/$_name"
        done
done
sconfigs="${sconfigs# }"
echo "sconfigs=[$sconfigs]”
=== END FILE: sconfigs2.sh ===

Given the above code, we can handle the afore-mentioned edge-casen:

$ ./sconfigs2.sh dhclient
sconfigs=[/etc/rc.conf.d/dhclient /usr/local/etc/rc.conf.d/dhclient /etc/rc.conf.d/network /usr/local/etc/rc.conf.d/network]
$ ./sconfigs2.sh initrandom
sconfigs=[/etc/rc.conf.d/random /usr/local/etc/rc.conf.d/random]
$ ./sconfigs2.sh postrandom
sconfigs=[/etc/rc.conf.d/random /usr/local/etc/rc.conf.d/random]
$ ./sconfigs2.sh swaplate
sconfigs=[/etc/rc.conf.d/swap /usr/local/etc/rc.conf.d/swap]
$ ./sconfigs2.sh local_unbound
sconfigs=[/etc/rc.conf.d/local_unbound /usr/local/etc/rc.conf.d/local_unbound]
$ ./sconfigs2.sh 
 does not exist in /etc/rc.d or the local startup
directories (/usr/local/etc/rc.d)
$ ./sconfigs2.sh nosuchservice
nosuchservice does not exist in /etc/rc.d or the local startup
directories (/usr/local/etc/rc.d)


This is looking like a much better approach.



> 
>> IMO such tools would be useful even for ansible/puppet and friends.
>> Not just for users ;)
>> 
> 
> Teaching service to use sysrc might be useful. Making sysrc detect that
> /etc/rc.conf.d/<service> exists, and use it, else fallback to
> /etc/rc.conf might also be nice.
> 
> service mysql-server enable|disable
> 	sets mysql_enable="YES|NO"
> service mysql-server set datadir=/var/db/mysql
> 	sets mysql_datadir="/var/db/mysql"
> 
> I am currently doing all of this in puppet with sysrc.

Adding a “service … enable” and “service … disable” could be nice.
I’m sure some of the above logic would be instrumental in that.
— 
Devin


More information about the freebsd-hackers mailing list