An archive of nntp://inn.qnx.com/bm45iq$m42$1@inn.qnx.com which had now gone non-public ...

$Keywords: QNX qnxrtp Momentics shutdown ATX power supply APM $

From: <kabe*sra-tohoku.co.jp>
Newsgroups: qdn.public.qnxrtp.x86
Subject: [SOURCE] shutdownx 1.0: alternate shutdown util with APM poweroff
Date: Fri, 10 Oct 2003 02:39:17 +0900
Organization: messy enough to have no footspace
Lines: 1388
Message-ID: <bm45iq$m42$1@inn.qnx.com>
X-Complaints-To: usenet@inn.qnx.com
NNTP-Posting-Date: 9 Oct 2003 17:23:38 GMT
X-Newsreader: mnews [version 1.22PL5] 2002-11-27(Wed)
Xref: vega.sra-tohoku.co.jp qdn.public.qnxrtp.x86:283

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(&reg, 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, &reg, 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(&reg, 0, sizeof(reg)); X reg.eax = APM_DISCONNECT; X reg.ebx = APM_DEV_APM_BIOS; /*APM BIOS*/ X s = _intr_v86(APM_SYSTEM_BIOS, &reg, 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(&reg, 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, &reg, 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(&reg, 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, &reg, 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(&reg, 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, &reg, 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(&reg, 0, sizeof(reg)); X reg.eax = APM_DISCONNECT; X reg.ebx = APM_DEV_APM_BIOS; /*APM BIOS*/ X s = _intr_v86(APM_SYSTEM_BIOS, &reg, 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:PackageType>Application</QPM:PackageType> X <QPM:PackageRepository></QPM:PackageRepository> X <QPM:FileVersion>1.9</QPM:FileVersion> X </QPM:PackageDescription> X X <QPM:ProductDescription> X <QPM:ProductName>shutdownx</QPM:ProductName> X <QPM:ProductIdentifier>shutdownx</QPM:ProductIdentifier> X <QPM:ProductEmail></QPM:ProductEmail> X <QPM:VendorName>Public</QPM:VendorName> X <QPM:VendorInstallName>vega</QPM:VendorInstallName> X <QPM:VendorURL></QPM:VendorURL> X <QPM:VendorEmbedURL></QPM:VendorEmbedURL> X <QPM:VendorEmail></QPM:VendorEmail> X <QPM:AuthorName>Taketo Kabe</QPM:AuthorName> X <QPM:AuthorURL></QPM:AuthorURL> X <QPM:AuthorEmbedURL></QPM:AuthorEmbedURL> X <QPM:AuthorEmail>kabe#sra-tohoku.co.jp</QPM:AuthorEmail> X <QPM:ProductIconSmall></QPM:ProductIconSmall> X <QPM:ProductIconLarge></QPM:ProductIconLarge> X <QPM:ProductDescriptionShort>configurable shutdown</QPM:ProductDescriptionShort> X <QPM:ProductDescriptionLong>shutdownx 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:ReleaseVersion>1.0</QPM:ReleaseVersion> X <QPM:ReleaseUrgency>Low</QPM:ReleaseUrgency> X <QPM:ReleaseStability>Stable</QPM:ReleaseStability> X <QPM:ReleaseNoteMinor></QPM:ReleaseNoteMinor> X <QPM:ReleaseNoteMajor></QPM:ReleaseNoteMajor> X <QPM:CountryExclude></QPM:CountryExclude> X <QPM:ReleaseCopyright>QNX 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:ContentKeyword>shutdown,poweroff,halt</QPM:ContentKeyword> X <QPM:TargetOS>qnx6</QPM:TargetOS> X <QPM:HostOS>none</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]; i++) { X if (!strcmp(trues[i], 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