Stock “shutdown” command can’t power off the ATX supply.
I tried numerous things to hook APM BIOS calls during shutdown
with no success.
So I whipped up a custom shutdown command which have
some expandability, and builtin APM power-off capability.
I believe the shutdown sequence resembles that of the stock shutdown.
Usage:
/usr/sbin/shutdownx -S poweroff
Build:
make
Make a package:
make package
Note: you can’t replace phshutdown, but it seems to work from Photon
without serious screws.
kabe
#!/bin/sh
This is shutdownx, a shell archive (produced by GNU sharutils 4.2.1)
To extract the files from this archive, save it to some FILE, remove
everything before the !/bin/sh' line above, then type sh FILE’.
Made on 2003-10-10 02:32 JST by <kabe@sra-tohoku.co.jp>.
Source directory was `/autofs/home/kabe/qnx/x’.
Existing files will not be overwritten unless `-c’ is specified.
This shar contains:
length mode name
------ ---------- ------------------------------------------
338 -rw-rw-r-- shutdownx/Makefile
6253 -rw-rw-r-- shutdownx/apmoff.c
4292 -rw-rw-r-- shutdownx/package.qpg
24937 -rw-rw-r-- shutdownx/shutdown.c
echo=echo
if touch -am -t 200112312359.59 $$.touch >/dev/null 2>&1 && test ! -f 200112312359.59 -a -f $$.touch; then
shar_touch=‘touch -am -t $1$2$3$4$5$6.$7 “$8”’
elif touch -am 123123592001.59 $$.touch >/dev/null 2>&1 && test ! -f 123123592001.59 -a ! -f 123123592001.5 -a -f $$.touch; then
shar_touch=‘touch -am $3$4$5$6$1$2.$7 “$8”’
elif touch -am 1231235901 $$.touch >/dev/null 2>&1 && test ! -f 1231235901 -a -f $$.touch; then
shar_touch=‘touch -am $3$4$5$6$2 “$8”’
else
shar_touch=:
echo
$echo ‘WARNING: not restoring timestamps.  Consider getting and’
$echo “installing GNU `touch’, distributed in GNU File Utilities…”
echo
fi
rm -f 200112312359.59 123123592001.59 123123592001.5 1231235901 $$.touch
if mkdir _sh01621; then
$echo ‘x -’ ‘creating lock directory’
else
$echo ‘failed to create lock directory’
exit 1
fi
============= shutdownx/Makefile ==============
if test ! -d ‘shutdownx’; then
$echo ‘x -’ ‘creating directory’ ‘shutdownx’
mkdir ‘shutdownx’
fi
if test -f ‘shutdownx/Makefile’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘shutdownx/Makefile’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘shutdownx/Makefile’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘shutdownx/Makefile’ &&
X
PROGRAM=shutdownx
OBJS = shutdown.o apmoff.o
X
CFLAGS=-Wc,-Wall -DUSE_BUILTIN_APMOFF=1
CC = qcc
CCLINK=$(CC)
X
all: $(PROGRAM)
X
$(PROGRAM): $(OBJS)
X	$(CCLINK) -o $@ $(OBJS) $(LDFLAGS)
X
clean::
X	rm -f *.o a.out core
X	rm -f $(PROGRAM)
X
package: package.qpg $(PROGRAM)
X	packager -f . package.qpg
clean::
X	rm -f *.qpm *.qpk
X	rm -f *.qpr
SHAR_EOF
(set 20 03 09 04 20 55 05 ‘shutdownx/Makefile’; eval “$shar_touch”) &&
chmod 0664 ‘shutdownx/Makefile’ ||
$echo ‘restore of’ ‘shutdownx/Makefile’ ‘failed’
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'shutdownx/Makefile'"
test 338 -eq “$shar_count” ||
$echo ‘shutdownx/Makefile:’ ‘original size’ ‘338,’ ‘current size’ “$shar_count!”
fi
============= shutdownx/apmoff.c ==============
if test -f ‘shutdownx/apmoff.c’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘shutdownx/apmoff.c’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘shutdownx/apmoff.c’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘shutdownx/apmoff.c’ &&
/*
X * apmoff.c
X * APM power shutdown
X *
X * for QNX RTP(x86)
X /
#if QNXNTO && X86
/ Ok, QNXRTP on x86 platform /
#else
#error Only for x86 architecture.
#endif
X
#if USE_BUILTIN_APMOFF /{/
X
#include <stdio.h>
#include <sys/types.h>	/u_short/
#include <string.h>	/strcmp/
#include <x86/v86.h>
X
/NetBSD-ish defines/
/APM/
#define APM_SYSTEM_BIOS		0x15	/int 15h/
#define APM_DEV_APM_BIOS	0x0000	/for BX in int 15h BIOS call/
#define APM_INSTALLATION_CHECK	0x5300	/for AX/
#define APM_32BIT_CONNECT	0x5303
#define APM_DISCONNECT		0x5304
#define APM_SET_POWER_STATE	0x5307
#define APM_GET_POWER_STATUS	0x530a
#define APM_DRIVER_VERSION	0x530e	/set version of this driver/
#define APM_GET_CAPABILITIES	0x5310
struct {
X	u_short	apm_flags;
X	u_short	apm_version;		/ BCD xx.xx /
X	u_short apm_driver_version;	/ BCD xx.xx /
} apm_info;
X
/ for struct _v86reg /
#define PSL_C	1	/ carry flag mask /
X
#define BH(reg)	((((reg).ebx)>>8)&0xff)
#define BL(reg)	(((reg).ebx)&0xff)
#define CH(reg)	((((reg).ecx)>>8)&0xff)
#define CL(reg)	(((reg).ecx)&0xff)
X
X
#define DPRINTF(x) if (option.verbose>=2) { printf x; } /**/
X
static struct {
X    int verbose;
X    int dryrun;
X    enum {Standby=1, Suspend=2, Off=3} setstate; / CX of Set Power State /
X    int wait_sigterm;
} option = {
X	.verbose = 0,
X	.dryrun = 0,
X	.setstate = Off,
X	.wait_sigterm = 0
};
X
/ apmoff(): connect and issue APM power state command
X * verbose_level: >=1 for verbosity
X * next_state: “off” “standby” “suspend”
X * dryrun: boolean; nonzero to do nothing
X */
int
apmoff(int verbose_level, char next_state, int dryrun)
{
X	struct _v86reg reg;
X	int	s;
X
X	option.verbose = verbose_level;
X	option.setstate = Off; / default /
X	option.dryrun = dryrun;
X
X	if (!stricmp(next_state, “standby”)) {
X		option.setstate = Standby;
X	} else
X	if (!stricmp(next_state, “suspend”)) {
X		option.setstate = Suspend;
X	}
X
X	/ installation check /
X	DPRINTF((“APM: Installation Check\n”));
X	memset(®, 0, sizeof(reg));
X	reg.eax = APM_INSTALLATION_CHECK;	/APM Installation Check/
X	reg.ebx = APM_DEV_APM_BIOS;		/APM BIOS/
X	s = _intr_v86(APM_SYSTEM_BIOS, ®, NULL,0);
X	if (s || reg.efl&PSL_C) { fprintf(stderr,“error on _intr_v86\n”); return s; }
X	/ result:
X	 * AH.AL: APM version number (BCD)
X	 * BH.BL: “PM”
X	 * CX: 0 has16bit
X	 *     1 has32bit
X	 *     2 Idle slows CPU
X	 *     3 disable
X	 *     4 disengage
X	 /
X	DPRINTF(("  APM Supported: %s\n", (reg.efl&PSL_C)?“no”:“yes”));
X	apm_info.apm_version = reg.eax;
X	apm_info.apm_flags = reg.ecx;
X	DPRINTF(("  BX signature=<%c%c>%s\n",
X	       (int)BH(reg), (int)BL(reg),
X	       ((reg.ebx&0xffff)==(‘P’<<8)+‘M’)?" (ok)":" (MISMATCH)"));
X	DPRINTF(("  BIOS APM version %x.%x\n",
X	         (apm_info.apm_version>>8)&255, apm_info.apm_version&255 ));
X	if(apm_info.apm_flags & 1) { DPRINTF(("  16bit mode interface\n")); }
X	if(apm_info.apm_flags & 2) { DPRINTF(("  32bit mode interface\n")); }
X	if(apm_info.apm_flags & 4) { DPRINTF(("  Slow CPU on Idle\n")); }
X	DPRINTF(("  BIOS PM %s\n", (apm_info.apm_flags&8)?“disabled”:“enabled”));
X	DPRINTF(("  BIOS PM %s\n", (apm_info.apm_flags&16)?“disengaged”:“engaged”));
X
X	if ((reg.ebx&0xffff) != (‘P’<<8)+‘M’) {
X		fprintf(stderr, “APM is not supported on this machine.\n”);
X		return 1;
X	}
X
X	/ disconnect first, in case somebody was connected /
X	memset(®, 0, sizeof(reg));
X	reg.eax = APM_DISCONNECT;
X	reg.ebx = APM_DEV_APM_BIOS;		/APM BIOS/
X	s = _intr_v86(APM_SYSTEM_BIOS, ®, NULL,0);
X	if (s) { fprintf(stderr,“error on _intr_v86\n”); return s; }
X	/if (reg.efl&PSL_C) { fprintf(stderr, “APM: disconnect failed\n”); return 1; }/
X
X	/ connect 32 /
X	DPRINTF((“APM: Connect32\n”));
X	memset(®, 0, sizeof(reg));
X	reg.eax = APM_32BIT_CONNECT;
X	reg.ebx = APM_DEV_APM_BIOS;		/APM BIOS/
X	s = _intr_v86(APM_SYSTEM_BIOS, ®, NULL,0);
X	if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
X	if (reg.efl&PSL_C) {
X		fprintf(stderr, “APM: Connect32 failed (code 0x%02lX)\n”, reg.eax);
X		return 1;
X	}
X	/
X	 * AX: code32 realmode CS
X	 * (E)BX: entry point offset
X	 * CX: code16 realmode CS
X	 * ESI: 16 code32 length-1
X	 *      16 code16 length-1
X	 * DX: data realmode DS
X	 * DI: data length
X	 /
X	DPRINTF(("  32Bit entry=%04X:%04X (len %04X+1)\n", (int)reg.eax, (int)reg.ebx, (int)(reg.esi&0xffff)));
X	DPRINTF(("  16Bit entry=%04X:%04X (len %04X+1)\n", (int)reg.ecx, (int)reg.ebx, (int)((reg.esi>>16)&0xffff)));
X	DPRINTF(("  Data region=%04X:( 0) (len %04X+1)\n", (int)reg.edx, (int)reg.edi));
X
X	/ set my version (needed to access 1.1- BIOS calls) (APM 1.1)  /
X	apm_info.apm_driver_version = 0x0101;	/ set to APM 1.1 /
X	DPRINTF((“APM: set Driver Version to %x.%x\n”,
X	         (apm_info.apm_driver_version>>8)&255, apm_info.apm_driver_version&255
X	         ));
X	memset(®, 0, sizeof(reg));
X	reg.eax = APM_DRIVER_VERSION;
X	reg.ebx = APM_DEV_APM_BIOS;
X	reg.ecx = apm_info.apm_driver_version;
X	s = _intr_v86(APM_SYSTEM_BIOS, ®, NULL,0);
X	/ result:
X	 * AH.AL: APM connection version
X	 /
X	if (s) { fprintf(stderr,“error on _intr_v86\n”); return s; }
X	if (reg.efl&PSL_C) {
X		fprintf(stderr, “APM: set Driver Version failed (code 0x%02X)\n”, (int)(reg.eax>>8));
X		/ what should I do? /
X		/ some BIOS seems to accept 1.1 calls regardess of error,
X		 * so just continue for now /
X		/exit(1);/
X	} else {
X		DPRINTF(("  Connected as version %x.%x\n", (int)((reg.eax>>8)&255), (int)(reg.eax&255) ));
X	}
X
X	/ set power status (to OFF etc) (APM 1.1) /
X	DPRINTF((“APM: Set Power State (All, %d)\n”, option.setstate));
X	memset(®, 0, sizeof(reg));
X	reg.eax = APM_SET_POWER_STATE;
X	reg.ebx = APM_DEV_APM_BIOS +0x0001;	/ all devices /
X	reg.ecx = option.setstate;
X	if (!option.dryrun) {
X	    s = _intr_v86(APM_SYSTEM_BIOS, ®, NULL,0);
X	}
X	if (s) { fprintf(stderr,“error on _intr_v86\n”); return s; }
X	if (reg.efl&PSL_C) {
X		fprintf(stderr, “APM: Set Power State failed (code 0x%02X)\n”, (int)(reg.eax>>8));
X		goto off_fail;
X	}
X	/ NOTREACHED */
off_fail:
X	goto disconnect;
X
X	return 0;
X
disconnect:
X	DPRINTF((“APM: Disconnect\n”));
X	memset(®, 0, sizeof(reg));
X	reg.eax = APM_DISCONNECT;
X	reg.ebx = APM_DEV_APM_BIOS;		/APM BIOS/
X	s = _intr_v86(APM_SYSTEM_BIOS, ®, NULL,0);
X	if (s) { fprintf(stderr,“error on _intr_v86\n”); return s; }
X	return s;
}
X
#endif /USE_BUILTIN_APMOFF }/
SHAR_EOF
(set 20 03 09 04 20 55 05 ‘shutdownx/apmoff.c’; eval “$shar_touch”) &&
chmod 0664 ‘shutdownx/apmoff.c’ ||
$echo ‘restore of’ ‘shutdownx/apmoff.c’ ‘failed’
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'shutdownx/apmoff.c'"
test 6253 -eq “$shar_count” ||
$echo ‘shutdownx/apmoff.c:’ ‘original size’ ‘6253,’ ‘current size’ “$shar_count!”
fi
============= shutdownx/package.qpg ==============
if test -f ‘shutdownx/package.qpg’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘shutdownx/package.qpg’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘shutdownx/package.qpg’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘shutdownx/package.qpg’ &&
QPG:Generation
X   QPG:Options
X      <QPG:User unattended=“yes” verbosity=“2” listfiles=“yes”/>
X      <QPG:Defaults type=“qnx_package”/>
X      QPG:Source</QPG:Source>
X      <QPG:Release number="+"/>
X      QPG:Build</QPG:Build>
X      <QPG:FileSorting strip=“no”/>
X      <QPG:Package targets=“combine”/>
X      <QPG:Repository generate=“yes”/>
X      QPG:FinalDir</QPG:FinalDir>
X      QPG:Cleanup</QPG:Cleanup>
X   </QPG:Options>
X
X   QPG:Responsible
X      QPG:Company</QPG:Company>
X      QPG:Department</QPG:Department>
X      QPG:Group</QPG:Group>
X      QPG:Team</QPG:Team>
X      QPG:Employee</QPG:Employee>
X      QPG:EmailAddress</QPG:EmailAddress>
X   </QPG:Responsible>
X
X   QPG:Values
X      QPG:Files
X         <QPG:Add file="./shutdownx" install="/opt/sbin/"/>
X      </QPG:Files>
X
X      QPG:PackageFilter
X         QPM:PackageManifest
X            QPM:PackageDescription
X               QPM:PackageTypeApplication</QPM:PackageType>
X               QPM:PackageRepository</QPM:PackageRepository>
X               QPM:FileVersion1.9</QPM:FileVersion>
X            </QPM:PackageDescription>
X
X            QPM:ProductDescription
X               QPM:ProductNameshutdownx</QPM:ProductName>
X               QPM:ProductIdentifiershutdownx</QPM:ProductIdentifier>
X               QPM:ProductEmail</QPM:ProductEmail>
X               QPM:VendorNamePublic</QPM:VendorName>
X               QPM:VendorInstallNamevega</QPM:VendorInstallName>
X               QPM:VendorURL</QPM:VendorURL>
X               QPM:VendorEmbedURL</QPM:VendorEmbedURL>
X               QPM:VendorEmail</QPM:VendorEmail>
X               QPM:AuthorNameTaketo Kabe</QPM:AuthorName>
X               QPM:AuthorURL</QPM:AuthorURL>
X               QPM:AuthorEmbedURL</QPM:AuthorEmbedURL>
X               QPM:AuthorEmailkabe#sra-tohoku.co.jp</QPM:AuthorEmail>
X               QPM:ProductIconSmall</QPM:ProductIconSmall>
X               QPM:ProductIconLarge</QPM:ProductIconLarge>
X               QPM:ProductDescriptionShortconfigurable shutdown</QPM:ProductDescriptionShort>
X               QPM:ProductDescriptionLongshutdownx is an alternate shutdown command, which could have configurable order of terminating the processes, and optional powerdown feature.</QPM:ProductDescriptionLong>
X               QPM:ProductDescriptionURL</QPM:ProductDescriptionURL>
X               QPM:ProductDescriptionEmbedURL</QPM:ProductDescriptionEmbedURL>
X            </QPM:ProductDescription>
X
X            QPM:ReleaseDescription
X               QPM:ReleaseVersion1.0</QPM:ReleaseVersion>
X               QPM:ReleaseUrgencyLow</QPM:ReleaseUrgency>
X               QPM:ReleaseStabilityStable</QPM:ReleaseStability>
X               QPM:ReleaseNoteMinor</QPM:ReleaseNoteMinor>
X               QPM:ReleaseNoteMajor</QPM:ReleaseNoteMajor>
X               QPM:CountryExclude</QPM:CountryExclude>
X               QPM:ReleaseCopyrightQNX Open Community License</QPM:ReleaseCopyright>
X            </QPM:ReleaseDescription>
X
X            QPM:ContentDescription
X               <QPM:ContentTopic xmlmultiple=“true”>System/Startup and Shutdown</QPM:ContentTopic>
X               QPM:ContentKeywordshutdown,poweroff,halt</QPM:ContentKeyword>
X               QPM:TargetOSqnx6</QPM:TargetOS>
X               QPM:HostOSnone</QPM:HostOS>
X               <QPM:DisplayEnvironment xmlmultiple=“true”>None</QPM:DisplayEnvironment>
X               <QPM:TargetAudience xmlmultiple=“true”>Administrator</QPM:TargetAudience>
X            </QPM:ContentDescription>
X
X            QPM:LicenseUrl</QPM:LicenseUrl>
X         </QPM:PackageManifest>
X      </QPG:PackageFilter>
X
X      <QPG:PackageFilter proc=“none” target=“none”>
X         QPM:PackageManifest
X            QPM:ProductInstallationDependencies
X               QPM:ProductRequirements</QPM:ProductRequirements>
X            </QPM:ProductInstallationDependencies>
X         </QPM:PackageManifest>
X      </QPG:PackageFilter>
X
X      <QPG:PackageFilter proc=“x86” target=“none”>
X         QPM:PackageManifest
X            QPM:ProductInstallationDependencies
X               QPM:ProductRequirements</QPM:ProductRequirements>
X            </QPM:ProductInstallationDependencies>
X         </QPM:PackageManifest>
X      </QPG:PackageFilter>
X   </QPG:Values>
</QPG:Generation>
SHAR_EOF
(set 20 03 09 04 20 50 28 ‘shutdownx/package.qpg’; eval “$shar_touch”) &&
chmod 0664 ‘shutdownx/package.qpg’ ||
$echo ‘restore of’ ‘shutdownx/package.qpg’ ‘failed’
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'shutdownx/package.qpg'"
test 4292 -eq “$shar_count” ||
$echo ‘shutdownx/package.qpg:’ ‘original size’ ‘4292,’ ‘current size’ “$shar_count!”
fi
============= shutdownx/shutdown.c ==============
if test -f ‘shutdownx/shutdown.c’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘shutdownx/shutdown.c’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘shutdownx/shutdown.c’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘shutdownx/shutdown.c’ &&
/*
X *	alternate shutdown command
X *
X * Goodies:
X *	Configurable order,signal,timeout of killing processes
X *	Able to invoke external program during shutdown
X *	Builtin APM poweroff
X *
X * shutdown3: use shutdown.conf
X * each line of shutdown.conf is member of linked list
X * which in turn has linked list of process belonging to the line
X /
static const char _rcsid[] = “$Id: shutdown.c,v 1.13 2003/10/09 01:27:12 kabe Exp $”;
X
/#define USE_BUILTIN_APMOFF 1*/
X
#include <stdio.h>
#include <unistd.h>	/getopt/
#include <ctype.h>	/tolower/
#include <string.h>	/strdup/
#include <stdlib.h>	/strtoul/
#include <libgen.h>	/basename/
#include <dirent.h>	/DIR,opendir,readdir/
#include <fcntl.h>	/* O_RDONLY*/
#include <malloc.h>
#include <signal.h>	/SIGTERM/
#include <sys/neutrino.h>
#include <sys/procfs.h>
#include <sys/procmgr.h>	/procmgr_daemon/
#include <sys/sysmgr.h>	/sysmgr_reboot/
#include <spawn.h>	/spawn/
X
struct {
X	int verbose;
X	int dryrun;
} global = {
X	.verbose = 1,	/* TODO offset /
X	.dryrun =0,
};
/ verbose level:
X * quiet(0)
X * default(1)
X * -v (2)
X /
X
/
X * Format considerations:
X *	ordering
X *	prefix matching (longest match prevail?)
X *		desired order: :other / * / :daemon / ?*
X *	signal spec
X *	wait spec
X *	“catch-all” for apps
X *	“sid==1” for daemon
X *	stop & halt if “-S system”
X *	wait before reboot?
X *	shutdown level (system/user/photon)
X *	invoke external program
X *	comment line
X * TODO
X *	get rid or make global.verbose more sane (currently off by one)
X *	don’t use /bin/sh when no glob/redirect/quote needed
X *	(spawned shellscript has “/bin/sh” as argv[0]; how to specify?)
X *	support for user-defined shutdown mode (predicate)
X *	don’t overload “:pause” for sleep and prompt
X *	check return of kill for no privilege
X *	read pidfile?
X *	-S user|photon
X *	update utmp? (stock shutdown says it does but actually doesn’t)
X *	=kill procs invoked by :sh (need to wait?) → use :rescan explicitly…
X *	parallel wait during kill
X /
X
static const char default_shutdown_conf[] =
X	“# Builtin default shutdown.conf\n”
X	“#:sh /etc/rc.d/rc.shutdown %S\n”
X	“:echo Shutting down apps…\n”
X	“# kill init first to prevent respawning login\n”
X	“init	sig=SIGINT\n”
/ tinit blocks HUP&TERM, will be zombie on INT|KILL /
X	“tinit	sig=SIGINT,wait=800\n”
X	“# shells\n”
X	"-	sig=SIGHUP\n"
X	“# other apps\n”
X	“:other\n”
X	“# daemon\n”
X	“:echo Shutting down daemons…\n”
X	/"#:select name= sid=1\n"/
X	“:daemon\n”
X	“# fsys\n”
X	“:echo Syncing filesystems…\n”
X	“:sync\n”
X	“:echo Shutting down filesystems…\n”
X	"devb-	wait=8000\n"
X	“devf-*	wait=8000\n”
X	“pipe\n”
X	“:if system :echo-n Shutdown complete.\nPress Enter to reboot…\n”
X	“:if system :pause\n”
X	“# Photon\n”
X	“photon\n”
X	“# disp drivers\n”
X	“:echo Shutting down display drivers…\n”
X	“phfont*\n”
X	“fontsleuth\n”
X	“pwm\n”
X	“devg-\n"
X	"devi-\n”
X	“io-gr*\n”
X	“fs-\n"
X	"pci-\n”
X	“devc-con	nokill	# otherwise messages will stop\n”
X	“devc-\n"
#if USE_BUILTIN_APMOFF
X	“:if poweroff :echo Power down…\n”
X	“:if poweroff :pause wait=300	# yield to console driver\n”
X	“:if poweroff :apmoff\n”
#else
X	“apmdriver	sig=SIGPWR\n”	/ not implemented */
#endif
X	“:echo Rebooting…\n”
X	“:pause wait=1000	# needs to yield to console driver\n”
X	“:reboot\n”
;
X
void do_shutdown(const char *shutdown_conf, const char *trues[]);
X
int
main(int argc, char *argv[])
{
X	int	c;
X	char *shutdown_conf = NULL;
X	struct {
X	    int count;
X	    const char *a[8];
X	} trues = {1, {“reboot”, NULL} };
X	const char *shutdown_mode = &trues.a[0];
X
X	while (EOF != (c = getopt(argc, argv, “bpDS:vqc:d”))) {
X	switch (c) {
X	  case ‘b’: / no reboot; synonym of -S system */
X	  	shutdown_mode = “system”; break;
X	  case ‘p’: / poweroff (from NetBSD) */
X	  	*shutdown_mode = “poweroff”; break;
X	  case ‘S’:
X	  	switch(tolower(optarg[0])) {
X	  	  case ‘r’: *shutdown_mode = “reboot”; break;
X	  	  case ‘s’: *shutdown_mode = “system”; break;
X		  case ‘p’: shutdown_mode = “poweroff”; break;
X	  	  default:
X			fprintf(stderr, “Invalid shutdown type (%s).\n”, optarg);
X			exit(1);
X	  	}
X	  	break;
X	  case ‘D’:	/ dryrun /
X	  	global.dryrun++;
X	  	break;
X	  case ‘v’:	/ verbose /
X	  	global.verbose++;
X	  	break;
X	  case ‘c’:	/ specify shutdown.conf /
X	  	shutdown_conf = optarg;
X	  	break;
X	  case ‘d’:	/ dump default conf /
X	  	exit(fputs(default_shutdown_conf, stdout));
X	  	break;
X	  /case ‘n’: node/
X	  /case ‘f’: fast/
X	  case ‘q’:	/ quiet /
X	  	global.verbose=0;
X	  	break;
X	  default:
X		return execlp(“use”, “use”, argv[0], NULL);
X		/NOTREACHED/
X	}/esac/
X	}/wend getopt/
X
X	/ add some predicate values /
X	if (global.dryrun     && trues.count<8-1) trues.a[trues.count++]=“dryrun”;
X	if (global.verbose>=2 && trues.count<8-1) trues.a[trues.count++]=“verbose”;
X	if (global.verbose==0 && trues.count<8-1) trues.a[trues.count++]=“quiet”;
X	trues.a[trues.count] = NULL;
X
X	/ now, do the shutdown things /
X	do_shutdown(shutdown_conf, trues.a);
X	return 0;
}
X
/
X * Escape “\n” into “\n” and copy into “to”
X * XXX target buffer overflow
X */
char *
unescape(/return/ char *to, register const char *from)
{
X	if (to == NULL) {
X		fprintf(stderr,“unescape: target NULL??\n”);
X		return to;
X	}
X	while (*from) {
X		if (*from == ‘\’) {
X			from++;
X			if (isdigit(from)) {
X				/ \0 \14 */
X				int oct = (*from++)-‘0’;
X				if (isdigit(*from)) oct=(oct<<3)+(*from++)-‘0’;
X				if (isdigit(*from)) oct=(oct<<3)+(*from++)-‘0’;
X				*to++ = oct;
X				continue;
X			}
X
X			switch(*from) {
X			  case ‘n’: *to++ = ‘\n’; from++; continue;
X			}
X		}
X		*to++ = from++;
X	}
X	to = ‘\0’; /terminate/
X	return to;
}
X
#define I(x) “\033[4m” x “\033[0m”
#define B(x) “\033[1m” x “\033[0m”
static const char _usage[] attribute((section(“QNX_usage”),unused)) =
“%C - alternate shutdown utility\n”
“\n”
“%C [-vDd] [-S system|reboot|poweroff] [-c shutdown.conf]\n”
“Options:\n”
"  -v    Verbose.\n"
"  -D    Dryrun.\n"
"  -S system|reboot|poweroff\n"
"        Final action. Default is “reboot”.\n"
#if USE_BUILTIN_APMOFF
"        “poweroff” uses a builtin APM driver facility;\n"
"        do not use it if you have a dedicated APM resmgr running.\n"
#endif
"  -c FILE\n"
"        Use alternate shutdown.conf other than the builtin default.\n"
"  -d    Dump out the builtin shutdown.conf.\n"
“\n”
“shutdown.conf format:\n”
"  # COMMENT\n"
"        Ignored.\n"
"  NAME [sig=SIGxxxx] [wait=N] [nokill]\n"
"        Send SIGTERM (or SIGxxxx) to a process named NAME\n"
"        and wait for N millisecs (default 5000) before sending SIGKILL.\n"
"        nokill designates not to terminate this process.\n"
"  NAME [sig=SIGxxxx] [wait=N] [nokill]\n"
"        Ditto, but prefix match.\n"
"  :echo MESSAGE\n"
"        Output MESSAGE. \n is translated to newline.\n"
"  :echo-n MESSAGE\n"
"        Output MESSAGE without trailing newline.\n"
"  :daemon [sig=SIGxxxx] [wait=N]\n"
"        Select all processes with session-ID==1.\n"
"  :other  [sig=SIGxxxx] [wait=N]\n"
"        Select all other processes.\n"
"  :pause\n"
"        Wait for Enter key to be pressed.\n"
"  :pause wait=N\n"
"        Wait for N millisecs.\n"
"  :sh COMMAND ARGS…\n"
"  	Invoke an external command.\n"
"  :rescan\n"
"  	Rescan the process table for new process.\n"
"  	You will need this if previous :sh forked something.\n"
"  :reboot\n"
"        Kernel Reboot.\n"
#if USE_BUILTIN_APMOFF
"  :apmoff\n"
"        Issue power-off to APM BIOS.\n"
#endif
"  :if [!]PREDICATE COMMAND\n"
"        Simple “if”. COMMAND can be any of the above.\n"
"        Valid PREDICATE will be 0,1, or option parameter to -S. \n"
"        All others evaluate to false.\n"
"        Example:\n"
"        :if system :echo Today is a good day to die.\n"
“\n”
“See also:\n   shutdown(1), sysmgr_reboot(2)\n”
“\n”;
#undef B
#undef I
X
/ A struct representing a single shutdown.conf line */
struct confline_t {
X	struct confline_t *cf_next;
X	char cf_spec;	/ filename prefix, or message /
X	enum {
X		CONF_EXACT, CONF_PREFIX, CONF_DAEMON, CONF_OTHER,
X		CONF_ECHO,
X		CONF_PAUSE,
X		CONF_RESCAN,
X		CONF_SYNC,
X		CONF_REBOOT,
X		CONF_APMOFF,
X		CONF_SHELL,
X	} cf_type;
X	int	cf_wait;	/ in millisecs /
X	int	cf_signal;	/ signal to send */
X	struct procent_t cf_memberproc; 	/ list of processes /
};
X
/ “cfs_” is for bunch of conflines, “cf_” is for a single confline /
X
/
X * cfs_parse_shutdown_conf:
X *	create a list of conflines from shutdown.conf
X *
X * conf: filename of shutdown.conf. NULL uses the builtin default table.
X * trues: NULL terminated string array which deemed “true” in predicate
X * return: linked list of whole conflines
X */
struct confline_t *
cfs_parse_shutdown_conf(const char * conf, const char * const trues[])
{
X	struct {
X	    enum {SRC_FILE,SRC_STRING} srctype;
X	    union {
X		const char *confstr;
X		FILE *fid;
X	    } d;
X	    int linenum;
X	} source;
X	struct confline_t conflist = NULL;	/ return */
X	struct confline_t *conflist_tailv = &conflist; / where to connect the new line */
X
X	if (conf==NULL) {
X		source.srctype = SRC_STRING;
X		source.d.confstr = default_shutdown_conf;
X		source.linenum = 0;
X	} else {
X		source.srctype = SRC_FILE;
X		source.d.fid = fopen(conf, “r”);
X		if (!source.d.fid) {
X			fprintf(stderr, “Cannot open %s\n”, conf);
X			return NULL;
X		}
X		source.linenum = 0;
X	}
X
X	while (1 /fgets(fid) != NULL/) {
X		struct confline_t newconf;
X		char buf[1024];
X		char *token, *_keeper;
X
X		if (source.srctype == SRC_FILE) {
X			char *p;
X			if (NULL == fgets(buf, sizeof buf, source.d.fid)) break /while/;
X			if ((p =strrchr(buf, ‘\r’))) p = ‘\0’;
X			if ((p =strrchr(buf, ‘\n’))) p = ‘\0’;
X			source.linenum++;
X		} else
X		if (source.srctype == SRC_STRING) {
X			char ptr; size_t len;
X			ptr = strchr(source.d.confstr,’\n’);
X			if (ptr==NULL) / no line / break /while/;
X			len = ptr-source.d.confstr;
X			if (len > (sizeof buf)-1) len = (sizeof buf)-1; /clamp/
X			memcpy(buf, source.d.confstr, len);
X			buf[len] = ‘\0’; /terminate,clobbering \n/
X			source.d.confstr = ptr+1; /next line/
X			source.linenum++;
X		} else {
X			/ can’t happen /
X			fprintf(stderr, “barf,barf,barf\n”); exit(1);
X		}
X		/ now buf[] holds a conf line /
X
X		/ set defaults…/
X		newconf.cf_spec = NULL;
X		newconf.cf_type = CONF_EXACT;
X		newconf.cf_wait = 5000; / 5000ms /
X		newconf.cf_signal = SIGTERM;
X		newconf.cf_memberproc = NULL;
X		newconf.cf_next = NULL;
X
X		token = strchr(buf, ‘#’);
X		if (token) token=’\0’; / clobber comment /
X		/ initial token /
X		token = strtok_r(buf, " \t", &_keeper);
X		if (NULL == token) goto _next_line;
X
X		/ build newconf from the line /
X
X		_reparse_top:
X		if (token[0] == ‘:’) {
X			/ command line /
X			int echo_newline = 1;
X			if ((echo_newline=1, !strcmp(token, “:echo”)) ||
X			    (echo_newline=0, !strcmp(token, “:echo-n”))) {
X			    	char str = strtok_r(NULL, “”, &_keeper); /get all remaining/
X				newconf.cf_spec = malloc(strlen(str)+2);
X				newconf.cf_type = CONF_ECHO;
X				newconf.cf_wait = 0;
X				unescape(newconf.cf_spec, str);
X				if (echo_newline) strcat(newconf.cf_spec, “\n”);
X			} else
X			if (!strcmp(token, “:sh”)) {
X				/ external command /
X				char str = strtok_r(NULL, “”, &_keeper);
X				newconf.cf_spec = malloc(strlen(str)+2);
X				newconf.cf_type = CONF_SHELL;
X				newconf.cf_wait = 0;
X				unescape(newconf.cf_spec, str);
X			} else
X			if (!strcmp(token, “:other”)) {
X				newconf.cf_type = CONF_OTHER;
X			} else
X			if (!strcmp(token, “:daemon”)) {
X				newconf.cf_type = CONF_DAEMON;
X			} else
X			if (!strcmp(token, “:pause”)) {
X				newconf.cf_type = CONF_PAUSE;
X				newconf.cf_wait = 0;
X			} else
X			if (!strcmp(token, “:rescan”)) {
X				newconf.cf_type = CONF_RESCAN;
X				newconf.cf_wait = 20; / wait a bit to yield /
X			} else
X			if (!strcmp(token, “:sync”)) {
X				newconf.cf_type = CONF_SYNC;
X				newconf.cf_wait = 50; / wait a bit to yield /
X			} else
X			if (!strcmp(token, “:reboot”)) {
X				newconf.cf_type = CONF_REBOOT;
X				newconf.cf_wait = 0;
X			} else
X			if (!strcmp(token, “:apmoff”)) {
X				newconf.cf_type = CONF_APMOFF;
X				newconf.cf_wait = 0;
X			} else
X			if (!strcmp(token, “:if”)) {
X				/ predicates are evaluated on parse stage /
X				/ :if 1 :echo hogehoge /
X				int apple;
X				int invert = 0;
X				token = strtok_r(NULL, " \t", &keeper);
X				while (token[0]==’!’) {
X					invert = !invert;
X					token++;
X					if (token==’\0’) / stray "! " /
X					    token = strtok_r(NULL, " \t", &_keeper);
X				}
X				if (!strcmp(token, “0”))
X					apple = 0;
X				else if (!strcmp(token, “1”))
X					apple = 1;
X				else {
X					int i;
X					/ textual predicate */
X					apple = 0;
X					for (i=0; trues; i++) {
X						if (!strcmp(trues, token)) {
X							/ matched /
X							apple = 1;
X							break;
X						}
X					}/next i/
X				}
X				if (invert) apple = !apple;
X			  	if (apple) {
X			  		/predicate true/
X				  	if (global.verbose>=3) printf(“Line %d: predicate true\n”, source.linenum);
X				  _reparse_command:
X				  	token = strtok_r(NULL, " \t", &_keeper);
X				  	goto _reparse_top;
X				} else {
X					/ predicate false /
X				  	if (global.verbose>=3) printf(“Line %d: predicate false\n”, source.linenum);
X					goto _next_line;
X				}
X				/NOTREACHED/
X			} else {
X				fprintf(stderr, “Line %d: Unknown directive “%s”; ignoring\n”, source.linenum, token);
X				goto _next_line;
X			}
X		} else {
X			/ procname glob match /
X			char p;
X			newconf.cf_spec = strdup(token);
X			p = strchr(newconf.cf_spec, '’);
X			if (p) {
X				/ prefix match/
X				/ devc-
X				 *      ^p /
X				p = ‘\0’; / clobber '’ /
X				newconf.cf_type = CONF_PREFIX;
X			} else {
X				/ exact match /
X				newconf.cf_type = CONF_EXACT;
X			}
X		}
X
X		/ get_options: /
X		/ tinit sig=SIGINT
X		 *       ^ptr /
X		while (NULL != (token = strtok_r(NULL, " \t,", &_keeper))) {
X			/ tinit sig=SIGINT wait=8000
X			 *       ^ptr       ^ptr */
X			if (!strncmp(token, “sig=”, 4)) {
X				const struct { char name; int sig; } st,
X				    sigtab[] = {
X					{ “HUP”,	SIGHUP },
X					{ “INT”,	SIGINT },
X					{ “TERM”,	SIGTERM },
X					{ “KILL”,	SIGKILL },
X					{ “PWR”,	SIGPWR },
X					{ NULL, 0 }
X				};
X				/ signal /
X				token+=4; / skip “sig=” /
X				if (!strncmp(token, “SIG”, 3)) token+=3; / skip “SIG” prefix /
X				for (st=sigtab; st->name; st++ ) {
X					if (!strcmp(token, st->name)) {
X						newconf.cf_signal = st->sig;
X						break;
X					}
X				}
X				if (!st->name) { /falloff/
X					/ numeric? /
X					fprintf(stderr, “Line %d: Unrecognized sig=%s directive\n”, source.linenum, token);
X					goto _next_line;
X				}
X			}/sig=/
X			else if (!strncmp(token, “wait=”, 5)) {
X				/ wait /
X				token+=5;
X				newconf.cf_wait = strtoul(token,&token,0);
X			} else if (!strcmp(token, “nokill”)) {
X				newconf.cf_signal = 0;
X			} else {
X				fprintf(stderr, “Line %d: Unrecognized option “%s”\n”, source.linenum, token);
X				goto _next_line;
X			}
X		}/wend options/
X
X		/ got_confline: */
X		{
X		struct confline_t *aconf = malloc(sizeof (struct confline_t));
X		if (aconf==NULL) { fprintf(stderr, “Out of memory\n”); exit(1); }
X		aconf = newconf;
X		/ append to list (not prepend) /
X		conflist_tailv = aconf;
X		conflist_tailv = &aconf->cf_next;
X		}
X	   _next_line: ;
X	}/ wend fgets/
X
X	if (source.srctype==SRC_FILE && source.d.fid) fclose(source.d.fid);
X
X	return conflist;
}
X
struct procent_t {
X	struct procent_t *pe_next;
X	char pe_name;
X	pid_t pe_pid;
X	pid_t pe_sid;
};
X
/ Create new procent from pid
X * return: new procent
X /
struct procent_t *
ent_frompid(pid_t pid)
{
X	struct procent_t ent;
X	/filedes_t/int fd;
X	char buf[512];
X	procfs_info	pinfo;
X	daddr_t	loc_argv0;
X
X	sprintf(buf, “/proc/%lu/as”, (long)pid);
X
X	fd = open(buf, O_RDONLY);
X	if (fd < 0) return NULL;
X
X	if (devctl(fd, DCMD_PROC_INFO, &pinfo, sizeof pinfo, NULL) != 0 ||
X	    lseek(fd, pinfo.initial_stack+sizeof(int) /&argv/, SEEK_SET) == -1 ||
X 	    read(fd, &loc_argv0, sizeof loc_argv0) /argv/ != sizeof loc_argv0 ||
X 	    lseek(fd, loc_argv0, SEEK_SET) /argv[0]/ == -1 ||
X 	    (read(fd, buf, sizeof buf)) <= 0 ||
X	    0) {
X		close(fd);
X		return NULL;
X	}
X
X	close(fd);
X
X	ent = malloc(sizeof(struct procent_t));
X	if (ent == NULL) {
X		fprintf(stderr, “Insufficient memory to build process list.\n”);
X		exit(1);
X	}
X	buf[(sizeof buf)-1] = ‘\0’;
X	ent->pe_name = strdup(basename(buf));
X	ent->pe_pid  = pid;
X	ent->pe_sid  = pinfo.sid;
X	ent->pe_next = NULL;
X
X 	return ent;
}
X
void
ent_free(struct procent_t this)
{
X	free(this->pe_name);
}
X
/
X * cfs_assign_proc_to_conf:
X *	Assigns the given process to a matching line within conflines.
X * confs: linked lines of whole conflines
X * ent:   process description
X * return: (assigned confline); NULL if no matching line
X */
struct confline_t *
cfs_assign_proc_to_conf(struct confline_t confs, struct procent_t ent)
{
X	int	matchlen = -99;
X	struct confline_t assign_to = NULL;
X
X	/ Desired order for zero-length match:
X	 * ? :daemon * :other
X	 /
X
X	for ( ; confs; confs=confs->cf_next) {
X		switch (confs->cf_type) {
X			int n;
X		  case CONF_EXACT:
X			n = strlen(confs->cf_spec);
X		  	if (!strcmp(ent->pe_name, confs->cf_spec) &&
X			    matchlen <= n) /is eq or better match/ {
X				assign_to = confs;
X				matchlen = n;
X		  	}
X		  	break;
X		  case CONF_PREFIX:
X		  	n = strlen(confs->cf_spec);
X		  	if (!strncmp(ent->pe_name, confs->cf_spec, n) &&
X		  	    matchlen <= n) / is eq or better match / {
X		  	    	assign_to = confs;
X		  	    	matchlen = n;
X		  	}
X			break;
X		  case CONF_DAEMON:
X		  	if (ent->pe_sid == 1 && matchlen <= 0) {
X		  		assign_to = confs;
X		  		matchlen = 1; / just above "" /
X		  	}
X		  	break;
X		  case CONF_OTHER:
X		  	if (matchlen <= -99) {
X		  		assign_to = confs;
X		  		matchlen = -99;
X		  	}
X		  	break;
X		  default:
X		  	/ ignore non-proc line /
X		  	break;
X		}/esac/
X	}/next confs/
X	if (NULL == assign_to) {
X		fprintf(stderr, “Warning: proc <%s> won’t be shut down; check shutdown.conf\n”, ent->pe_name);
X		return NULL;
X	}
X	ent->pe_next = assign_to->cf_memberproc;
X	assign_to->cf_memberproc = ent;
X	// printf(“proc <%s> assigned to conf type %d <%s>\n”, ent->pe_name, assign_to->cf_type, assign_to->cf_spec?assign_to->cf_spec:"(null)");
X	return assign_to;
}
X
/ return value of cf_action() */
enum cf_stat { CFSTAT_OK=0, CFSTAT_RESCAN=9 };
X
static enum cf_stat
cf_action_echo(const struct confline_t *this)
{
X	if (global.verbose) fputs(this->cf_spec, stdout); fflush(stdout);
X	if (this->cf_wait) delay(this->cf_wait);
X	return CFSTAT_OK;
}
static enum cf_stat
cf_action_shell(const struct confline_t *this)
{
X	int s;
X	struct inheritance inherit;
X	char nargv[4];
X	if (global.verbose>=2) { printf(“Invoking <%s>\n”, this->cf_spec); }
X
X	/ XXX: we want to enable SIGHUP,SIGINT for children… /
X	inherit.flags = SPAWN_SETSIGDEF;
X	sigfillset(&inherit.sigdefault);
X	/ use “sh -c …”, assuming /bin/sh is still available… /
X	nargv[0] = “sh”; nargv[1] = “-c”; nargv[2] = this->cf_spec;
X	nargv[3] = NULL;
X	s = spawn("/bin/sh", 0,NULL, &inherit, nargv, NULL);
X
X	/ reload /proc/ in case it forked something;
X	 * but this seldom works because /proc/ isn’t updated immediately */
X	return CFSTAT_RESCAN;
}
static enum cf_stat
cf_action_pause(const struct confline_t this)
{
X	/ TODO: prompting and plain delay() should be a different command */
X	if (this->cf_spec && global.verbose) {
X		fputs(this->cf_spec, stdout); fflush(stdout);
X	}
X	if (this->cf_wait==0) {
X		getchar();
X	} else {
X		delay(this->cf_wait);
X	}
X	return CFSTAT_OK;
}
static enum cf_stat
cf_action_rescan(const struct confline_t this)
{
X	if (this->cf_wait) delay(this->cf_wait);
X	/ “this” doesn’t know the whole confline_t top, so just return
X	 * rescan errorcode here. Toplevel cfs loop should catch this
X	 * and issue cfs_rescan_procs(whole_conf) */
X	return CFSTAT_RESCAN;
}
static enum cf_stat
cf_action_sync(const struct confline_t *this)
{
X	if (this->cf_wait) delay(this->cf_wait);
X	if (global.verbose>=2) printf(“Issue sync()\n”);
X	sync();
X	return CFSTAT_OK;
}
static enum cf_stat
cf_action_reboot(const struct confline_t *this)
{
X	if (this->cf_wait) delay(this->cf_wait);
X	if (global.dryrun) return 0;
X	if (geteuid() != 0) {
X		fprintf(stderr, “You must be root to shutdown the system.\n”);
X		return 1;
X	}
X	if (global.verbose>=2) printf(“Issue sysmgr_reboot()\n”);
X	sysmgr_reboot();
X	/NOTREACHED/
X	return CFSTAT_OK;
}
static enum cf_stat
cf_action_apmoff(const struct confline_t *this)
{
#if USE_BUILTIN_APMOFF
X	extern int apmoff(int verbose, char nextstate, int dryrun);
X	int v;
X	v = global.verbose-1; if (v<0) v=0;
X	apmoff(v, “off”, global.dryrun);
X	/ NOTREACHED */
#else
X	printf(“Builtin APM not configured.\n”);
#endif
X	return CFSTAT_OK;
}
static enum cf_stat
cf_action_kill(const struct confline_t *this)
{
X	struct procent_t ent;
X
X	for (ent = this->cf_memberproc; ent; ent=ent->pe_next) {
X		int waitcount;
X
X		if (global.verbose>=2) {
X			printf("   %s", ent->pe_name);
X			if (global.verbose>=3)
X				printf(" [%lu] wait=%d sig=%d", (long)ent->pe_pid, this->cf_wait, this->cf_signal);
X			printf("\n");
X		}
X
X		if (global.dryrun) continue;
X		if (this->cf_signal==0) continue; / nokill /
X
X		kill(ent->pe_pid, this->cf_signal);	/XXX check return/
X
X		waitcount=this->cf_wait;
X		while ( waitcount>0 ) {
X			if ( kill(ent->pe_pid, 0) ) goto _died;
X			delay(10); / 10ms wait /
X			waitcount -= 10;
X		}
X		if ( kill(ent->pe_pid, 0) == 0 ) {
X			/ still not dead, SIGKILL it */
X			printf(”\t%s (%lu) not responding, sending SIGKILL…\n",
X			    ent->pe_name, (long)ent->pe_pid);
X			kill(ent->pe_pid, SIGKILL);
X			break;
X		}
X	  _died: ;
X
X	}/next ent/
X	return CFSTAT_OK;
}
X
static enum cf_stat
cf_action(const struct confline_t this)
{
X	enum cf_stat s;
//XXX/if (global.verbose>=4) { printf(“action line <%s> type %d\n”, this->cf_spec?this->cf_spec:"(null)", this->cf_type); }
X	switch (this->cf_type) {
X	  case CONF_ECHO:
X		s = cf_action_echo(this);	break;
X	  case CONF_PAUSE:
X	  	s = cf_action_pause(this);	break;
X	  case CONF_REBOOT:
X		s = cf_action_reboot(this);	break;
X	  case CONF_APMOFF:
X	  	s = cf_action_apmoff(this);	break;
X	  case CONF_SHELL:
X	  	s = cf_action_shell(this);	break;
X	  case CONF_RESCAN:
X	  	s = cf_action_rescan(this);	break;
X	  case CONF_SYNC:
X	  	s = cf_action_sync(this);	break;
X	  case CONF_EXACT: / differences are in cfs_assign_proc_to_conf() /
X	  case CONF_PREFIX:
X	  case CONF_DAEMON:
X	  case CONF_OTHER:
X	  	s = cf_action_kill(this);	break;
X	}/esac/
X	return s;
}
X
/ rebuild the proclist from current /proc/ state */
struct confline_t *
cfs_rescan_procs(struct confline_t *confs)
{
X	struct confline_t *cfscan;
X	DIR *dir;
X	struct dirent dirent;
X	pid_t mypid;
X
X	/ first, clobber all procent in each line.
X	 * (does nothing for vanilla lines) */
X 	for (cfscan=confs; cfscan; cfscan=cfscan->cf_next) {
X 		struct procent_t *ent, x;
X 		for (ent=cfscan->cf_memberproc; ent; ent=x) {
X 			x = ent->pe_next;
X 			ent_free(ent);
X 		}
X 		cfscan->cf_memberproc = NULL;
X 	}
X
X	mypid = getpid();
X
X	/ scan through /proc , and
X	 * distribute procs into conflines->cf_memberproc */
X	dir = opendir("/proc");
X	if (dir == NULL) {
X		fprintf(stderr, “Unable to access procfs.\n”);
X		exit(1);
X	}
X	while (NULL != (dirent = readdir(dir))) {
X		struct procent_t ent;
X
X		pid_t pid = strtoul(dirent->d_name, NULL, 0);
X		if (pid == 1) continue; / procnto /
X		if (pid <= 0) continue; / not [0-9] /
X		if (pid == mypid) continue; / don’t kill myself */
X
X		ent = ent_frompid(pid);
X		if (NULL == ent) continue;
X		cfs_assign_proc_to_conf(confs, ent);
X
X	}/wend readdir/
X	closedir(dir);
X
X	return confs;
}
X
void
cfs_dokills(struct confline_t *conflines)
{
X	struct confline_t cf_scan;
X	/ start killing /
X	for(cf_scan=conflines; cf_scan; cf_scan=cf_scan->cf_next) {
X		switch(cf_action(cf_scan)) {
X		  case CFSTAT_OK: break;
X		  case CFSTAT_RESCAN:
X			//sleep(1);
X			cfs_rescan_procs(conflines);
X			break;
X		}
X		//cfs_rescan_procs(conflines); //XXXreload every time
X	}
X	/ return error status? */
}
X
void
do_shutdown(const char *shutdown_conf, const char *trues[])
{
X	struct confline_t *conflines = NULL;
X
X	conflines = cfs_parse_shutdown_conf(shutdown_conf, trues);
X	if (conflines == NULL) {
X		fprintf(stderr, “Failed parsing shutdown.conf\n”);
X		exit(1);
X	}
X
//{struct confline_t *cf;
//for (cf=conflines; cf; cf=cf->cf_next) {
//	printf(“type=%d spec=<%s>\n”, cf->cf_type, cf->cf_spec?cf->cf_spec:"(null)");
//}
//}
X	if (global.verbose>=3) {
X		const char **p;
X		printf(“Predicates:”);
X		for (p=trues; *p; p++) { printf(" <%s>", p); }
X		printf("\n");
X	}
X
X	/ I should be immune to parent (shell) death /
X	/
X	 * want to detach from parent process,
X	 * as parent shell sends HUP-HUP-KILL on exit,
X	 * but want stdin console input/stdout output
X	 /
X	signal(SIGTERM, SIG_IGN);
X	signal(SIGINT , SIG_IGN);
X
X	chdir("/");
X	if (!global.dryrun)
X	    if (fork()) exit(0);
X	/ now is child /
X	setsid();
X	// procmgr_daemon(0, /PROCMGR_DAEMON_NOCHDIR|/PROCMGR_DAEMON_NODEVNULL);
X	// if (fork()) exit(0); /wavier session leader/
X
X	/ fill in the conf with processes /
X	cfs_rescan_procs(conflines);
X
X	/ the show begins /
X	cfs_dokills(conflines);
}
/ use return status from cf_action instead of longjmp */
SHAR_EOF
(set 20 03 10 09 10 27 12 ‘shutdownx/shutdown.c’; eval “$shar_touch”) &&
chmod 0664 ‘shutdownx/shutdown.c’ ||
$echo ‘restore of’ ‘shutdownx/shutdown.c’ ‘failed’
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'shutdownx/shutdown.c'"
test 24937 -eq “$shar_count” ||
$echo ‘shutdownx/shutdown.c:’ ‘original size’ ‘24937,’ ‘current size’ “$shar_count!”
fi
rm -fr sh01621
exit 0