Gilbert Ashley 19 October 2010 On creating (mostly?) POSIX-compliant init scripts for Slackware, including the use of ash or dash as the shell to run them. These are notes to myself, but may be useful to anyone else considering the implementation of an alternative shell for use with Slackware init scripts. The goal of this project is *not* to replace /bin/sh with /bin/dash. Doing that would make me/us responsible for many obscure, hard-to-find breakages which are simply unwanted -including failure of configure scripts (especially from old sources) and failure of sundry scripts grabbed from who-knows-where. The init scripts in these sources are comprised of a combination of ideas and code from Sasha Alexander(sic?), Tom Goya and Gilbert Ashley. They are based on the standard Slackware init scripts from Slackware-12.1/12.2 (IIRC). In order to update them to include any changes made in the standard scripts since 12.1/12.2, create a diff of the official 12.1 and whatver later version you are comparing to. Then, review each of the patch 'hunks' to see if they need be applied. *** POSIX-compliant != ash != dash != sh(bash as sh): The meaning of 'POSIX-compliant' is undefined here. Assuming full (and 'pure') POSIX-compliance (with whichever POSIX version), is probably not the best goal to have for this project. Offering a viable alternative to using bash in the least intrusive manner should serve better and perhaps be more likely to get consideration from PatV for upstreaming any work into official Slackware (something that I highly doubt, BTW). Still, if one pretends to offer installable packgages or scripts to build them to others, a seamless way to upgrade and downgrade should be provided. dash is advertized as being POSIX-compliant, but may support one or two features which are not compliant(echo -n, $(commands) instead of `commands`). It has been awhile now since 12.1/12.2 were current and we three worked on this, so my recollection of specific differences has waned a bit... For our purposes, the main difference between Slackware's 'ash' and 'dash' is that dash supports $(), whereas ash does not. Check again about (echo -n ..) when using either. One would also need to check compatibility when sourcing sub-scripts which need the 'start' parameter passed to them. *** Comparsion of three approaches (and content) from Sasha, Tim and Gilbert: 1. Like Tim, I dislike the two-space indentation used in most of the original scripts, so diffing between his or my scripts against the standard ones (or between each other) will show up lots of 'white-space-only' changes which can certainly be ignored. (Tim likes using three-space indentation and I like four-space.) 2. Tim adopted another change which is mostly about style: that of using exclusively explicit 'test' statements, instead of the standard tests which use single brackets. If looking at diffs of Tim's scripts against the standard ones (or mine), most of the individual patch hunks that occur have to do with these 'test'-vs-'[]' syntax changes. Although many of the scripts I include are based on those by Tim, I did not use this alternative 'test' syntax. 3. Tim included several core-funtionality changes which can be adopted or not. a. rc.4 has a changed routine for checking and using a given display manager. b. rc.6 contains some 'echo -n' usage which may not be 'POSIX-compliant'. Using explicit calls to /bin/echo eliminates the problem (there aren't many of these, but good to review for them. c. rc.M loops through a list of 'services' to optionally run minor sub-scripts which are called individually in the standard scripts. (I like this approach, but wound up leaving it out for the time-being as being one-strutural-change-too-many. It does look nicer, it's easier to modify the order of execution and add services, and should be very slightly faster.) 4. Sasha's scripts included many extra rc.* files which are not part of the regular sysvinit or init-scripts packages from Slackware. They also include some customizations which are probably specific to her system. I also found a probable typo/bug in Sasha's rc.inet1 (missing closing ' or " around 110-117 IIRC) 5. Tim's modified scripts comprise the least number of files. At first, I only modified the basic scripts (rc.S, rc.M, etc) along with some of the most basic 'optional' ones which promised to make the most difference in lowering bootup times, like rc.udev. I also tackled a few older or more complex ones which were not so likely to be used, but are specifically called by the main scripts and offered considerable chance for faster run-times, like rc.serial and rc.scanluns(the real changes are in rescan-scsi-bus.sh) I later started modifying many of the standard scripts, but because of packaging and maintenance considerations, I scaled down the scope of the endeavour to only encompass the main scripts included in the standard package and the important ones which offered most benefit and/or were called directly by the main scripts. Anything started after messagebus and hald in rc.M should be non-critical and can be handled by un-modified scripts, if properly called by our main scripts. 6. There is some syntax inconsistency and variation in the standard scripts which makes things a little confusing at times -especially regarding the sourcing of subscripts with and without the 'start' parameter. Some scripts adapted from debian or redhat-style systems, and other very old standard ones have differences in syntax vis-a-vis 'case $action in' vs. 'case $1 in'. Another thing to be careful of is sub-scripts which contain any 'exit' statements -sourcing them in any manner will cause early abortion of the init scripts. Sasha (later) came up with a method using function calls to enable sourcing sub-scripts with parameters. However, doing this implies assuring that any such sub-scripts be syntax-compliant with whatever shell we are hoping to use for the init scripts. I had used a syntax like this: action=start /etc/rc.d/rc.subscript which relied on modifying each sub-script to support the use of the $action variable. Many standard scripts use 'start' as the default parameter if none is given, but not all of them. My scripts still contain lines like: action=${action:-$1} even though I found a possibly better solution. First, executing the scripts like this: /etc/rc.d/rc.subscript start makes them work okay. But, that is bad if any routines fail or contain an 'exit'. So, I wound up explicitly calling them using 'sh /etc/rc.d/rc.subscript start'. But, since some of these sub-scripts are ones that are altered to use our chosen 'init shell', calling them with 'sh' wastes several cycles/time is lost resoving the path to 'sh' which then loads a script which may have '#!/bin/our-favourite-init-shell' as the shebang, which wastes more time starting our-favourite-init-shell to run the code. Some sub-scripts which are called without any parameters may be using the default 'action' as explained above. But, most of them are scripts which indeed need no paramters. Most rc.program scripts which are supplied by the individual package are like this. My solution to this wound up being the use of an explicit shebang for our chosen init shell which is used for any sub-scripts which are modified to use ash/dash and are included with the distributed scripts package. Any other subscripts which need a separate shell, or that formerly were sourced with a supplied parameter, are instead explicitly called like this: sh /etc/rc.d/rc.subscript start or: sh /etc/rc.d/rc.subscript sub-scripts which are modified for ash/dash are called like this: /bin/our-favourite-init-shell /etc/rc.d/rc.subscript start 7. Because of compatibility and upgrade/downgrade considerations, I settled on using scripts which are run by the shell named /bin/initsh. This allows us to be flexible and use dash, ash or bash as the shell for our init processes, without having to replace bash-as/bin/sh as the main system 'sh' -doing so would never happen in Slackware and would cause loads of grief like what debian/mandriva had when they change to using 'dash' as their /bin/sh. I wanted to be able to create a package which could be installed and used with dash(if installed), ash(if installed) or bash (as standard). So the doinst.sh script and naming-convention of the files in my packages are adjusted to this functionality. My main sysvinit package was also slightly changed to fit with this idea. For the fastest execution of the init scripts, one should copy or rename the chosen shell as /bin/initsh. Having /bin/initsh as a link to ash or dash will slow down things somewhat as resoving links takes time(just as the standard scheme having /bin/sh as a link to /bin/bash). 8. My scripts also contain a few hunks of changes which are non-standard. My rc.udev includes some code which allows for use with both older and newer versions of udev, which can be ignored(udevcontrol reload_rules vs udevadm control --reload-rules). 9. I found some compat issues in wildcard path expansion in rc.udev (grep for 'for TMPFILE in') and (maybe?)in rc.S at '# Clean up some temporary files:' (check use of constructs like: '*/*/*' and 'kde-[a-zA-Z]*' 10. Both Tim and Sasha did extensive re-writes of one or main scripts -each with their own ideas/methods. One in particular is rc.inet1. I wound up using Tim's as the base for mine, although I now don't remember exactly why I chose his. 11. The doinst.sh and sysvinit-scripts.src2pkg scripts included in my sources provide (among other things), a review or prioritization of many of the most important sub-scripts which are mostly supplied as part of their own package. In order to avoid any need to maintain a bunch of accessory scripts, I tried to come up with a way of calling all extra scripts in a generic way which would be compatible with whatever shebang and syntax they normally use. Their use is completely optional on most any system, so any possible bootup time savings from modifying them would have a minimal effect on boot time. Hence, it is easy and feasible to leave them out of the main init-scripts package. 12. Though my changes are structurally more extensive, they are/were still a Work In Progress. Using my scripts, I was able to achieve a boot-up time of 7-8 seconds on a 2.0 GHz Pentium IV machine, compared with a timf of around 30 seconds on the same machine using the standard scripts. Some of this (huge) time-saving is accounted for by commenting a 3-second sleep(waiting for USB drivers in rc.S) and by backgrounding the processes in rc.M of this type: *update-* The total savings of those is around 5-7 seconds, so YMMV regarding actual time-savings on your system. My system was also booting with very few optional services activated. 13. My packages still include some sub-scripts which should not be included in the main init-scripts package. Since I built packages for my own system, some of the content may be mixed as compared to current Slackware packges. I wavered between building packages like old-style Slackware packages where init scripts were included with sysvinit, instead of being in separate package(s). Notes on speed optimizations: A. Running in-line code is faster than calling a function which includes the code B. Calling a link is slower than calling the prgram directly. C. Calling a program without the absolute path is slower than calling it with full path as the system must resolve the real location from possible paths. D. Startup latency of dash or ash is negligible as compared to bash, so explicitly using separate instances of ash/dash is nearly as fast as sourcing a bash sub-script fragment or executing a script with /bin/sh shebang. I'm referring here to the tradeoffs between using: . /etc/rc.d/some.script start /etc/rc.d/some.script start /bin/initsh /etc/rc.d/some.script start The conevntion I used was to use the last alternative only for scripts which are specifically modified for ash/dash. Other cases should probably use: /bin/sh /etc/rc.d/some.script start for best results with unknown syntax or syntax in files you don't want to include in your mods. Occasionally, individuals will create or modify init scripts from other systems which contain an 'exit'. Directly executing the scripts with a separate instance of /bin/sh will avoid premature aborting of init routines if the scripts contain any 'exit' statements. E. Some of the scripts at the core of the *early* init process offer the best opportunity for speed optimization. rc.inet1 and rc.udev come to mind. Both have *lots* of 'if', 'else' checks which can be quite slow. F. Using 'case' statements is faster than using 'if', '[]' or 'test' G. Using shell features to replace some string-manipulation calls to sed and awk, will prove faster in every case (in these short init scripts), although the syntax may be less clear to most 'reviewers'. H. Using a modified installation of dash, where the main binary is named /bin/initsh will produce slightly faster times than having /bin/initsh as a link to /bin/dash. My efforts were not aimed at achieving the fastest bootup *at any cost*. Rather, I tried to create package content/structure/concepts which would provide good speed-ups while maintaining flexibility for use by others and adhere to Slackware 'just-works' and upgrade/downgrade concepts. The doinst.sh uses an 'abundance of caution' approach by creating backups of original bash-syntaxed files, so a user might recover a borked conversion to /bin/initsh scripts with a 'brute force' of renaming backed-up files to the original names. And I considered the various scenarios of using my scripts with no dash or ash present, or with either one of the two already present when installing my packages. *** At the time I was working on these scripts, I was also investigating general syntax differences between Slackware's ash, busybox's ash, (various) BSD ash versions, dash, bash 2.?, bash-3.? and others. There is no shell which is 100% POSIX-compliant and does not include any other features. dash does appear to be the closets thing, but includes a couple of extra features. Many people, when talking about POSIX-compliance, may actually mean compliance with traditional 'sh' or traditional 'Bourne shell'. Traditional sh is probably best represented by the 'sh' from the heirloom project ( on sourceforge). And traditional Bourne shell is probably best represented by the shell called 'jsh', when called as 'sh' or anything other than 'jsh'. Calling the shell as 'jsh' turns on job-control features, whereas calling it as 'sh' or 'bsh' will run it without job-control. Any effort to create very-cross-compatible shell code can be enhanced by testing the code against both jsh and the 'heirloom' sh. Both of them are very similar to the original Solaris 'sh' syntax which is still a viable criteria for many people. busybox ash is not equal to any other version of ash -but its' syntax and features should be considered for any scripts which might be included in an installer initrd or any system which might (foolishly?) try to use busybox ash as the main shell or as the shell used for init scripts. Other busybox shells like 'lash' are nowhere near similar or comaptible with anything else... The Slackware version of 'ash' is not the same as anyone else's. It is getting harder to get it to compile and at least one Slacker has reported it seg-faulting on Slackware64 when hitting 'backspace' more than once -I'm not sure if his 'fix' for the problem has been incorporated into the official packages. checkbashisms.pl helps find some obscure, hard-to-spot cases of 'iffy' code. If I had any perl 'fu' at all, I'd add a few lines and options to it to recognize a few of the differences mentioned below... *** Review of some questionable syntax to watch out for: a. Use of [-e FILE ] is not POSIX-compliant(I forget right now if dash/ash accept this. Use [-r FILE ] or [-x FILE ] instead. b. wildcard path expansion is 'flaky' with ash/dash. Using 'ls' with wildcards will usually work, but is not really a good idea either, for other reasons. c. ash doesn't support $() -use `` instead. dash does support $() d. sourcing files with a following parameter doesn't work with ash or dash e. watch out for 'echo -n' It's not used often in the scripts -changing to use /bin/echo should be an adequate fix. One might test using printf, but make sure both ash and dash give the desired results... f. watch out for comments appended to the end of code lines -ash and dash may accept them, but the syntax is not POSIX-compliant(IIRC). Mainly, I mention this because I find comments after code onerous to read. g. watch out for comparisons of unquoted variables. This works: [ 'some-value' != $UNQUOTED_VARIABLE ] where this does not: [ $UNQUOTED_VARIABLE != 'some-value' ] Still, I find the first example counter-intuitive to read. Using this: [ "QUOTED_VARIABLE" != 'some-value' ] should also always work. h. Slackware's init scripts have slowly become more and more bash-dependent, using arrays and expansions/substitutions which are not compatible with other shells. Tim and Sashas mods have addressed the worst cases of this. checkbashisms will identify these bad spots, but in particular, converting code to not use arrays is no easy matter. i. Using bash-style double brackets '[[]]' cures and hides some hard-to-spot and hard-to-fix cases where single brackets '[]' or 'test' might need to be used -maybe especially in common constructs which are using grep within brackets. Conversions to single brackets should be carefully tested. Tim submitted some code to me for use in src2pkg which used simple 'test' instead of any brackets and I never did find the way to make the test successful with any brackets at all. It still bothers me because it's the only code in src2pkg which does use simple 'test' instead of brackets. j. Be careful with any code using 'sed'. Older versions of 'standard' sed or busybox versions of sed will surely have differences when compared with modern sed. k. I found a spot where 'awk' is used in the init scripts. I eliminated it as I thought that requiring sed during boot-up to be 'over the top'. Follow your own heart... Have fun re-booting your machine with the install CD to temporarily be 'in business' again after experimenting with these scripts. :=) Or, use a VM or qemu to ease things abit. *DO NOT* blindly install packages built from my scripts without backing up your original /etc/rc.d directory! Some of my concepts may not have been fully implemented at the time I stopped working on these scripts. Still, using packages created using my src2pkg build scripts and the included doinst.sh scripts will be much better than simply copying these scripts into /etc/rc.d to replaces the standard ones. Especially because of the renaming and shebang conventions I have adopted, these scripts will simply leave your system unbootbale unless my doinst.sh is used.