[source] eject command, with unmount

As Igor have said, ejecting the CDROM is just one devctl();
everyone has a homebrew eject command, right?

But automating unmounting the concerned mounts was sort of a nightmare.
(not just /fs/cd0; you want /fs/cd0/boot/fs/qnxbase.qfs unmounted too)
The messy bottom half of the eject.c is an effort for this.

It’s working nicely for now.

Trying to eject an image file (non block device) seems to
lock you up for awhile. Dunno the reason

kabe

#!/bin/sh

This is 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-05-14 05:07 JST by <kabe@sra-tohoku.co.jp>.

Source directory was `/autofs/home/kabe/y’.

Existing files will not be overwritten unless `-c’ is specified.

This shar contains:

length mode name

------ ---------- ------------------------------------------

360 -rw-rw-r-- eject/Makefile

10885 -rw-rw-r-- eject/eject.c

3902 -rw-rw-r-- eject/eject.qpg

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 _sh12472; then
$echo ‘x -’ ‘creating lock directory’
else
$echo ‘failed to create lock directory’
exit 1
fi

============= eject/Makefile ==============

if test ! -d ‘eject’; then
$echo ‘x -’ ‘creating directory’ ‘eject’
mkdir ‘eject’
fi
if test -f ‘eject/Makefile’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘eject/Makefile’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘eject/Makefile’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘eject/Makefile’ &&

X

Compile options is in $(PROGRAM).c, not here

X
PROGRAM=eject
X
BINDIR=/opt/bin
X
all: $(PROGRAM)
X
$(PROGRAM): $(PROGRAM).c
X -sh $@.c
X
clean::
X rm -f $(PROGRAM)
X rm -f *.o core a.out *.bak *.vim *~
X
distclean:: clean
X rm -f *.qpr *.qpm *.qpk
X
install: $(PROGRAM)
X cp -p $(PROGRAM) $(BINDIR)
X
package: $(PROGRAM) $(PROGRAM).qpg
X packager -u $(PROGRAM).qpg
SHAR_EOF
(set 20 03 05 14 04 56 28 ‘eject/Makefile’; eval “$shar_touch”) &&
chmod 0664 ‘eject/Makefile’ ||
$echo ‘restore of’ ‘eject/Makefile’ ‘failed’
if ( md5sum --help 2>&1 | grep ‘sage: md5sum [’ ) >/dev/null 2>&1
&& ( md5sum --version 2>&1 | grep -v ‘textutils 1.12’ ) >/dev/null; then
md5sum -c << SHAR_EOF >/dev/null 2>&1
|| $echo ‘eject/Makefile:’ ‘MD5 check failed’
46400e39c8e285c0ecd2d6b8c2b5b371 eject/Makefile
SHAR_EOF
else
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'eject/Makefile'"
test 360 -eq “$shar_count” ||
$echo ‘eject/Makefile:’ ‘original size’ ‘360,’ ‘current size’ “$shar_count!”
fi
fi

============= eject/eject.c ==============

if test -f ‘eject/eject.c’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘eject/eject.c’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘eject/eject.c’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘eject/eject.c’ &&
#if shell
set -x
EXE=basename "$0" .c
${CC=qcc} -o $EXE
X ${CFLAGS="-Wc,-Wall -O2"}
X -D_STAMP=""$compiled: $LOGNAME@$HOSTNAME date +'%Y/%m/%d %T %z' $""
X $0
#usemsg -c $EXE $0
exit
#endif
X
/*
X * Eject/Load a CDROM tray
X /
X
#ifdef _STAMP
static const char _stamp[] = _STAMP;
#endif
static const char _cvsid[] = “$Id: eject.c,v 1.6 2003/05/12 18:23:00 kabe Exp $”;
X
#if ! QNXNTO
#error Only for QNXRTP.
#endif
X
#include <stdio.h>
#include <sys/types.h> /open(2)/
#include <fcntl.h> /open(2)/
#include <string.h>
#include <ctype.h> /isdigit/
#include <unistd.h> /getopt/
#include <sys/dcmd_cam.h>
X
struct {
X int verbose;
X int dryrun;
X int umount_flags;
} G = {
X .verbose=0,
X .dryrun =0,
X .umount_flags = 0 /
MOUNT_FORCE /
};
X
void umountall(const char devpath);
X
#define B(x) “\033[1m” x “\033[0m”
#define I(x) “\033[4m” x “\033[0m”
static const char _usage[] attribute ((section(“QNX_usage”),unused)) =
“%C - eject/load a CDROM drive tray or similar device\n”
“\n”
“%C [” B("-elqvn") “] [” I(“device”) “]\n”
“Options:\n”
" " B("-e") " Eject (default). Corresponding filesystems will be unmounted.\n"
" " B("-l") " Load the tray.\n"
" " B("-q") " Query for media existance. (Exit status 0 if available)\n"
" " B("-v") " Verbose.\n"
" " B("-n") " Dryrun. Useful to just show relevant mountpoints.\n"
“The [” I(“device”) “] defaults to " B(”/dev/cd0") “.\n”
“”;
#undef B
#undef I
X
int
main(int argc, char argv[])
{
X int fd;
X int c;
X enum { Eject, Load, Query } action = Eject;
X char devpath[PATH_MAX] = “/dev/cd0”;
X int s=0; /devctl status/
X
X while ((c = getopt(argc, argv, “leqvn”)) != EOF) {
X switch (c) {
X case ‘l’: action = Load; break;
X case ‘e’: action = Eject; break;
X case ‘q’: action = Query; break;
X case ‘v’: G.verbose++; break;
X case ‘n’: G.dryrun++; break;
X default: execlp(“use”, “use”, argv[0], NULL);
X /NOTREACHED/
X }
X }
X if (optind < argc) { strcpy(devpath, argv[optind++]); }
X
X
X /
devpath = “/dev/”.devpath.“0” if !m;^/; /
X if (devpath[0] != ‘/’) {
X /
not “/” prefixed; prepend “/dev” /
X memcpy(devpath+5, devpath, strlen(devpath));
X strncpy(devpath, “/dev/”, 5);
X if (!isdigit(devpath[strlen(devpath)-1])) {
X /
plain “cd” without number; assume "/dev/0" /
X strcat(devpath, “0”);
X }
X }
X
X /
now devpath will be “/dev/cd0” /
X /
(insert pathname check here for setuid installations) /
X
X if (G.verbose>=1) printf(“Using device %s\n”, devpath);
X
X fd = open(devpath, O_RDONLY);
X if (fd<0) {
X fprintf(stderr, “%s: “, argv[0]);
X perror(devpath);
X exit(1);
X }
X
X switch (action) {
X int dummy = 1;
X struct cam_devinfo devinfo;
X case Eject:
X umountall(devpath);
X if (G.verbose>=1) printf(“eject %s\n”, devpath);
X if (!G.dryrun) {
X s = devctl(fd, DCMD_CAM_EJECT_MEDIA, &dummy,sizeof(dummy), NULL);
X if (s!=0) { fprintf(stderr, “%s: %s: %s\n”, argv[0], devpath, strerror(s)); }
X }
X break;
X case Load:
X s = devctl(fd, DCMD_CAM_LOAD_MEDIA, &dummy,sizeof(dummy), NULL);
X if (s!=0) { fprintf(stderr, “%s: %s: %s\n”, argv[0], devpath, strerror(s)); }
X break;
X case Query:
X s = devctl(fd, DCMD_CAM_DEVINFO, &devinfo,sizeof(devinfo), NULL);
X if (s!=0) { fprintf(stderr, “%s: %s: %s\n”, argv[0], devpath, strerror(s)); }
X if (s == 0) {
X if (devinfo.flags & DEV_NO_MEDIA) {
X printf(”%s: no media\n”, devpath);
X s = 1; /
return value /
X } else {
X printf("%s is available\n", devpath);
X s = 0;
X }
X }
X break;
X }
X
X close(fd);
X
X return s;
}
X
/
mess to scan mountpoints and umount concerned paths ****/
X
#include <dirent.h> /opendir/
#include <sys/dcmd_blk.h> /*DCMD_FSYS
/
#include <sys/statvfs.h> /fstatvfs/
#include <sys/mount.h> /umount/
#include <malloc.h> /malloc/
#include <limits.h> /PATH_MAX/
#include <stdlib.h> /realpath/
#include <sys/netmgr.h> /netmgr_ndtostr()/
X
enum scan_devdir { NO_SUBPARTITION=1, SCAN_DEVDIR=0 };
/
NO_SUBPARTITION will supress scanning into /dev/,
X * which will only needed when finding subpartititons (/dev/hd0t77 in /dev/hd0),
X * but unlikely for ejectable media (/dev/cd0).
X * (whatabout zip disks?)
X /
X
/
device/mountpoint pairs gleaned from /proc/mount/ */
struct mtchain_t {
X char *mountpoint;
X char *device;
X int dev_mode;
X char mt_type[16];
X struct mtchain_t *next;
X struct mtchain_t parent;
} _mtchain = {
X .mountpoint = NULL, .device = NULL, .next = NULL, .parent = NULL
X /
if member changes, also change the member filling in scan_mount1() */
},
X mtchain = &_mtchain; / always use mtchain->next for initializer /
/
try to get rid of mtarray:
X instead of parent having ->sub, let children have ->parent.
X Scanning whole list then should be normal traversal of mtchain->next /
/
this may need to full-scan the list to get the dependency
X children, which is inefficient */
X
struct {
X int len;
X char path;
} qlocal = {0,NULL}; /
/net// /
X
void
umountall(const char devpath)
{
X char path[PATH_MAX] = “/proc/mount”; /
strlen==11 magic
/
X static void scan_mount1(char *procmountpath, enum scan_devdir scflag);
X static void reparent(void);
X static void umount_sub(const struct mtchain_t *mt);
X struct mtchain_t mtp;
X
X /
build linear linked dev/mount list /
X scan_mount1(path, NO_SUBPARTITION);
X
X reparent();
X
X /
Convert the passed devpath to qnet string, otherwise
X * “/dev/cd0” won’t match “/net//dev/cd0”
X * when qnet is active.
X /
X /
(reuse path[]) /
X if (0 < netmgr_ndtostr(ND2S_DIR_SHOW, ND_LOCAL_NODE, path, sizeof(path))) {
X qlocal.len = strlen(path);
X qlocal.path = strdup(path);
X } else {
X qlocal.len = 1;
X qlocal.path = “/”;
X }
X /
resolve intermediate symlinks, and
X * canonicalize devpath /net/myhost/ to /net/myhost.fq.dn/ /
X /
(NOTE: with /link → /abs/path symlink, /net/myhost/link
X * could resolve to non-qnet /abs/path ) /
X realpath(devpath, path);
X
X if (!strncmp(path, qlocal.path, qlocal.len)) {
X /
devpath is already qnet path /
X } else {
X /
prepend qlocal /
X /
(assume realpath resolves to “/…”, having / at head) /
X int n = qlocal.len;
X if (qlocal.path[qlocal.len-1] == ‘/’) n–;
X memmove(path+n, path, strlen(path)+1); /XXX overflow/
X strncpy(path, qlocal.path, n);
X }
X
X /
now path will be “/net//dev/cd0” /
X if (G.verbose>=2) { printf(“Device resolved to %s\n”, path); }
X
X /
find the tree trunk matching devpath, and
X * umount it /
X for (mtp=mtchain->next; mtp; mtp=mtp->next) {
X if (!strcmp(path, mtp->device)) {
X umount_sub(mtp);
X break;
X }
X }
X
X /
cleanup; not necessary? /
X for (mtp=mtchain->next; mtp; mtp=mtp->next) {
X free(mtp->mountpoint);
X free(mtp->device);
X free(mtp);
X }
}
X
/
Recursively descend /proc/mount and build dev/mountpoint pairs /
/
Chain the found mountpoints into mtchain->next list */
static void
scan_mount1(/volatile/ char procmountpath[], enum scan_devdir scflag)
{
X DIR *dir;
X struct dirent de;
X char p;
X
X dir = opendir(procmountpath);
X if (!dir) return;
X p = procmountpath + strlen(procmountpath); /
tail /
X
X while ((de = readdir(dir))) {
X int a1,a2,a3,a4,a5;
X int fd;
X struct stat statbuf;
X
X p = ‘/’;
X strcpy(p+1,de->d_name);
X
X if (stat(procmountpath, &statbuf) < 0) continue;
X
X /
,,,, /
X /
if the directory has this entry, the directory
X * is attached by some resmgr /
X if (5 != sscanf(de->d_name, “%d,%d,%d,%d,%d”, &a1,&a2,&a3,&a4,&a5)) {
X /
normal-ish entry /
X if (S_ISDIR(statbuf.st_mode)) {
X if (scflag==NO_SUBPARTITION &&
X !strcmp(procmountpath+11/magic/,"/dev"))
X continue;
X /recurse/
X scan_mount1(procmountpath, scflag);
X }
X continue;
X }
X if (a5 != 0) continue;
X /
,,
,
,0 /
X if (0 <= (fd = open(procmountpath, O_RDONLY)) ) {
X char buf[256];
X struct statvfs vfs; /
use fstatvfs() to determine mountpointness */
X if (0 <= fstatvfs(fd, &vfs) &&
X 0 <= devctl(fd, DCMD_FSYS_MOUNTED_ON, buf, sizeof(buf), NULL) ) {
X struct mtchain_t mt = malloc(sizeof(struct mtchain_t));
X /
fill *mt in this block /
X
X mt->device = strdup(buf);
X
X devctl(fd, DCMD_FSYS_MOUNTED_AT, buf,sizeof(buf), NULL);
X mt->mountpoint = strdup(buf);
X
X stat(mt->device, &statbuf);
X mt->dev_mode = statbuf.st_mode;
X
X strcpy(mt->mt_type, vfs.f_basetype);
X
X if (G.verbose>=2) printf("%s on %s at %s -t %s\n",
X procmountpath+11/magic/,
X mt->device, mt->mountpoint, mt->mt_type);
X /
chain the found mountpoint into list */
X mt->next = mtchain->next;
X mtchain->next = mt;
X mt->parent = NULL;
X }
X close(fd);
X }/open/
X }/readdir/
X p = ‘\0’;
X closedir(dir);
X return;
}
X
/
Rearrange the mtchain->next list according to mount dependencies /
/

X * dev /dev/cd0 at /fs/cd0
X * is ->parent of
X * dev /fs/cd0/boot/fs/qnxbase.qfs at /pkgs/base
X * dev /fs/cd0/boot/fs/qnxpub100.qfs at /fs/qnxpub100
X */
static void
reparent(void)
{
X struct mtchain_t mt;
X
X for (mt=mtchain->next; mt; mt=mt->next) {
X struct mtchain_t ps, candidate;
X int maxmatchlen = 0;
X
X /
block device stands for itself;
X * don’t reparent them under “/” /
X if (S_ISBLK(mt->dev_mode)) continue;
X if (!strcmp(mt->mt_type, “pkg”)) continue;
X
X /
scan for a longest mountpoint (/fs/cd0)
X * which mt->device (/fs/cd0/boot/a.qfs) belongs /
X candidate = NULL;
X for (ps=mtchain->next; ps; ps=ps->next) {
X int pmlen = strlen(ps->mountpoint);
X if (!strncmp(ps->mountpoint, mt->device, pmlen) &&
X maxmatchlen < pmlen &&
X (mt->device[pmlen]==’\0’ || /exact/
X ps->mountpoint[pmlen-1]==’/’ || /
/fs/cd0/ match /fs/cd0/boot… /
X mt->device[pmlen]==’/’ /
/fs/cd0 /fs/cd0/boot
/ )) {
X candidate = ps;
X maxmatchlen = pmlen;
X }
X }
X if (!candidate) {
X fprintf(stderr, “Warning: dangling device %s at %s\n”, mt->device, mt->mountpoint);
X continue;
X }
X if (candidate==mt) continue;
X /
have parent /
X mt->parent = candidate;
X if (G.verbose>=2) printf("(%s at %s) have (%s at %s)\n",
X candidate->device, candidate->mountpoint,
X mt->device, mt->mountpoint);
X }
}
X
/
umount things below mt, then umount itself */
static void
umount_sub(const struct mtchain_t *mt)
{
X const struct mtchain_t sub;
X
X /
unmount things below here /
X if (G.verbose>=1)
X printf(“Checking mounted device below %s\n”, mt->mountpoint);
X
X /
scan for children: full scan of those who has ->parent==this; inefficient /
X for (sub=mtchain->next; sub; sub=sub->next) {
X if (sub->parent != mt) continue;
X
X if (G.verbose>=1)
X printf("%s on %s is within %s\n",
X sub->device, sub->mountpoint, mt->mountpoint);
X umount_sub(sub); /recurse/
X }
X if (G.verbose>=0) {
X if (G.verbose>=0) printf(“umount %s”, mt->mountpoint);
X if (G.verbose>=1) printf("\t# device %s", mt->device);
X putchar(’\n’);
X }
X if (!G.dryrun) umount(mt->mountpoint, G.umount_flags);
}
X
/
TODO:
X * - eject /net/other.host/…
X * - better verbose diag, deleting /net/local.host
X */
SHAR_EOF
(set 20 03 05 13 03 23 00 ‘eject/eject.c’; eval “$shar_touch”) &&
chmod 0664 ‘eject/eject.c’ ||
$echo ‘restore of’ ‘eject/eject.c’ ‘failed’
if ( md5sum --help 2>&1 | grep ‘sage: md5sum [’ ) >/dev/null 2>&1
&& ( md5sum --version 2>&1 | grep -v ‘textutils 1.12’ ) >/dev/null; then
md5sum -c << SHAR_EOF >/dev/null 2>&1
|| $echo ‘eject/eject.c:’ ‘MD5 check failed’
fcdca84afba13837a4cbe9e34437c154 eject/eject.c
SHAR_EOF
else
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'eject/eject.c'"
test 10885 -eq “$shar_count” ||
$echo ‘eject/eject.c:’ ‘original size’ ‘10885,’ ‘current size’ “$shar_count!”
fi
fi

============= eject/eject.qpg ==============

if test -f ‘eject/eject.qpg’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘eject/eject.qpg’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘eject/eject.qpg’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘eject/eject.qpg’ &&
QPG:Generation
X QPG:Options
X <QPG:User unattended=“no” verbosity=“2” listfiles=“yes”/>
X QPG:Build</QPG:Build>
X <QPG:Repository generate=“yes”/>
X <QPG:FinalDir dir="." />
X </QPG:Options>
X
X QPG:Values
X QPG:Files
X <QPG:Add file="./eject" install="/opt/bin/"/>
X <QPG:Add file="./eject.c" install="/opt/share/src/eject/" component=“src” />
X <QPG:Add file="./eject.qpg" install="/opt/share/src/eject/" component=“src” />
X <QPG:Add file="./Makefile" install="/opt/share/src/eject/" component=“src” />
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:ProductNameeject</QPM:ProductName>
X QPM:ProductIdentifiereject</QPM:ProductIdentifier>
X QPM:VendorNamePublic</QPM:VendorName>
X QPM:VendorInstallNamepublic</QPM:VendorInstallName>
X QPM:VendorEmail</QPM:VendorEmail>
X QPM:AuthorNameTaketo Kabe</QPM:AuthorName>
X QPM:AuthorURL</QPM:AuthorURL>
X QPM:AuthorEmailkabe@sra-tohoku.co.jp</QPM:AuthorEmail>
X QPM:ProductDescriptionShorteject & umount removable device</QPM:ProductDescriptionShort>
X QPM:ProductDescriptionLong
eject will eject/load a CDROM tray or similar removable device.
X It will also umount any corresponding filesystems, including files
mounted as a filesystem.
X </QPM:ProductDescriptionLong>
X </QPM:ProductDescription>
X
X QPM:ReleaseDescription
X QPM:ReleaseVersion1.0</QPM:ReleaseVersion>
X QPM:ReleaseUrgencyMedium</QPM:ReleaseUrgency>
X QPM:ReleaseStabilityStable</QPM:ReleaseStability>
X QPM:ReleaseNoteMinor</QPM:ReleaseNoteMinor>
X QPM:ReleaseNoteMajor</QPM:ReleaseNoteMajor>
X QPM:ReleaseCopyrightBSD License</QPM:ReleaseCopyright>
X </QPM:ReleaseDescription>
X
X QPM:ContentDescription
X <QPM:ContentTopic xmlmultiple=“true”>System/Device Specific/Storage System</QPM:ContentTopic>
X QPM:ContentKeywordeject, unmount, umount, CDROM</QPM:ContentKeyword>
X QPM:TargetOSqnx6</QPM:TargetOS>
X QPM:HostOSnone</QPM:HostOS>
X <QPM:DisplayEnvironment xmlmultiple=“true”>Console</QPM:DisplayEnvironment>
X <QPM:TargetAudience xmlmultiple=“true”>User</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
X <QPG:PackageFilter component=“src”>
X QPM:PackageManifest
X QPM:ProductDescription
X QPM:ProductDescriptionLong
Source code for the eject utility.
X </QPM:ProductDescriptionLong>
X </QPM:ProductDescription>
X </QPM:PackageManifest>
X </QPG:PackageFilter>
X </QPG:Values>
</QPG:Generation>
SHAR_EOF
(set 20 03 05 13 03 30 44 ‘eject/eject.qpg’; eval “$shar_touch”) &&
chmod 0664 ‘eject/eject.qpg’ ||
$echo ‘restore of’ ‘eject/eject.qpg’ ‘failed’
if ( md5sum --help 2>&1 | grep ‘sage: md5sum [’ ) >/dev/null 2>&1
&& ( md5sum --version 2>&1 | grep -v ‘textutils 1.12’ ) >/dev/null; then
md5sum -c << SHAR_EOF >/dev/null 2>&1
|| $echo ‘eject/eject.qpg:’ ‘MD5 check failed’
9b46ee96093bbd0c41ecabbc3bb4d3de eject/eject.qpg
SHAR_EOF
else
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'eject/eject.qpg'"
test 3902 -eq “$shar_count” ||
$echo ‘eject/eject.qpg:’ ‘original size’ ‘3902,’ ‘current size’ “$shar_count!”
fi
fi
rm -fr _sh12472
exit 0

As Igor have said, ejecting the CDROM is just one devctl();
everyone has a homebrew eject command, right?

But automating unmounting the concerned mounts was sort of a nightmare.
(not just /fs/cd0; you want /fs/cd0/boot/fs/qnxbase.qfs unmounted too)
The messy bottom half of the eject.c is an effort for this.


Update from previous:

  • Fix fatal error on “eject -”.
  • “eject filename.img” will now work.

    kabe

#!/bin/sh

This is eject, 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-21 02:04 JST by <kabe@sra-tohoku.co.jp>.

Source directory was `/autofs/home/kabe/qnx/src’.

Existing files will not be overwritten unless `-c’ is specified.

This shar contains:

length mode name

------ ---------- ------------------------------------------

370 -rw-rw-r-- eject/Makefile

11512 -rw-rw-r-- eject/eject.c

3971 -rw-rw-r-- eject/eject.qpg

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 _sh08011; then
$echo ‘x -’ ‘creating lock directory’
else
$echo ‘failed to create lock directory’
exit 1
fi

============= eject/Makefile ==============

if test ! -d ‘eject’; then
$echo ‘x -’ ‘creating directory’ ‘eject’
mkdir ‘eject’
fi
if test -f ‘eject/Makefile’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘eject/Makefile’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘eject/Makefile’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘eject/Makefile’ &&

X

Compile options is in $(PROGRAM).c, not here

X
PROGRAM=eject
X
BINDIR=/opt/bin
X
all: $(PROGRAM)
X
$(PROGRAM): $(PROGRAM).c
X -sh $@.c
X
clean::
X rm -f $(PROGRAM)
X rm -f *.o core a.out *.bak *.vim *~
X
distclean:: clean
X rm -f *.qpr *.qpm *.qpk
X
install: $(PROGRAM)
X cp -p $(PROGRAM) $(DESTDIR)$(BINDIR)
X
package: $(PROGRAM) $(PROGRAM).qpg
X packager -u $(PROGRAM).qpg
SHAR_EOF
(set 20 03 10 15 05 25 54 ‘eject/Makefile’; eval “$shar_touch”) &&
chmod 0664 ‘eject/Makefile’ ||
$echo ‘restore of’ ‘eject/Makefile’ ‘failed’
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'eject/Makefile'"
test 370 -eq “$shar_count” ||
$echo ‘eject/Makefile:’ ‘original size’ ‘370,’ ‘current size’ “$shar_count!”
fi

============= eject/eject.c ==============

if test -f ‘eject/eject.c’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘eject/eject.c’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘eject/eject.c’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘eject/eject.c’ &&
#if shell
set -x
EXE=basename "$0" .c
${CC=qcc} -o $EXE
X ${CFLAGS="-Wc,-Wall -O2"}
X -D_STAMP=""$compiled: $LOGNAME@$HOSTNAME date +'%Y/%m/%d %T %z' $""
X $0
#usemsg -c $EXE $0
exit
#endif
X
/*
X * Eject/Load a CDROM tray
X /
X
#ifdef _STAMP
static const char _stamp[] = _STAMP;
#endif
static const char _cvsid[] = “$Id: eject.c,v 1.12 2003/10/14 20:24:36 kabe Exp $”;
X
#if ! QNXNTO
#error Only for QNXRTP.
#endif
X
#include <stdio.h>
#include <sys/types.h> /open(2)/
#include <fcntl.h> /open(2)/
#include <string.h>
#include <ctype.h> /isdigit/
#include <unistd.h> /getopt,access/
#include <sys/dcmd_cam.h>
X
struct {
X int verbose;
X int dryrun;
X int umount_flags;
} G = {
X .verbose=0,
X .dryrun =0,
X .umount_flags = 0 /
MOUNT_FORCE /
};
X
void umountall(const char devpath);
X
#define B(x) “\033[1m” x “\033[0m”
#define I(x) “\033[4m” x “\033[0m”
static const char _usage[] attribute ((section(“QNX_usage”),unused)) =
“%C - eject/load a CDROM drive tray or similar device\n”
“\n”
“%C [” B("-elqvn") “] [” I(“device”) “]\n”
“Options:\n”
" " B("-e") " Eject (default). Corresponding filesystems will be unmounted.\n"
" " B("-l") " Load the tray.\n"
" " B("-q") " Query for media existance. (Exit status 0 if available)\n"
" " B("-v") " Verbose.\n"
" " B("-n") " Dryrun. Useful to just show relevant mountpoints.\n"
“The [” I(“device”) “] defaults to " B(”/dev/cd0") “.\n”
B(“WARNING:”) " Ejecting a root disk (/dev/hd0t77) is not recommended.\n"
“”;
#undef B
#undef I
X
int
main(int argc, char argv[])
{
X int fd;
X int c;
X enum { Eject, Load, Query } action = Eject;
X char devpath[PATH_MAX] = “/dev/cd0”;
X int s=0; /devctl status/
X
X while ((c = getopt(argc, argv, “leqvn”)) != EOF) {
X switch (c) {
X case ‘l’: action = Load; break;
X case ‘e’: action = Eject; break;
X case ‘q’: action = Query; break;
X case ‘v’: G.verbose++; break;
X case ‘n’: G.dryrun++; break;
X default: execlp(“use”, “use”, argv[0], NULL);
X /NOTREACHED/
X }
X }
X if (optind < argc) { strcpy(devpath, argv[optind++]); }
X
X
X if (access(devpath, F_OK) == 0) {
X /
argument file exists (likely relative path) /
X /
use it as is /
X } else {
X /
devpath = “/dev/”.devpath.“0” if !m;^/; /
X if (devpath[0] != ‘/’) {
X /
not “/” prefixed; prepend “/dev” /
X memcpy(devpath+5, devpath, strlen(devpath)+1);
X strncpy(devpath, “/dev/”, 5);
X if (!isdigit(devpath[strlen(devpath)-1])) {
X /
plain “cd” without number; assume "/dev/0" /
X strcat(devpath, “0”);
X }
X }
X }
X
X /
now devpath will be “/dev/cd0” /
X /
(insert pathname check here for setuid installations) /
X
X if (G.verbose>=1) printf(“Using device %s\n”, devpath);
X
X fd = open(devpath, O_RDONLY);
X if (fd<0) {
X fprintf(stderr, “%s: “, argv[0]);
X perror(devpath);
X exit(1);
X }
X
X switch (action) {
X int dummy = 1;
X struct cam_devinfo devinfo;
X case Eject:
X umountall(devpath);
X if (G.verbose>=1) printf(“eject %s\n”, devpath);
X if (!G.dryrun) {
X s = devctl(fd, DCMD_CAM_EJECT_MEDIA, &dummy,sizeof(dummy), NULL);
X if (s!=0) { fprintf(stderr, “%s: %s: %s\n”, argv[0], devpath, strerror(s)); }
X }
X break;
X case Load:
X s = devctl(fd, DCMD_CAM_LOAD_MEDIA, &dummy,sizeof(dummy), NULL);
X if (s!=0) { fprintf(stderr, “%s: %s: %s\n”, argv[0], devpath, strerror(s)); }
X break;
X case Query:
X s = devctl(fd, DCMD_CAM_DEVINFO, &devinfo,sizeof(devinfo), NULL);
X if (s!=0) { fprintf(stderr, “%s: %s: %s\n”, argv[0], devpath, strerror(s)); }
X if (s == 0) {
X if (devinfo.flags & DEV_NO_MEDIA) {
X printf(”%s: no media\n”, devpath);
X s = 1; /
return value /
X } else {
X printf("%s is available\n", devpath);
X s = 0;
X }
X }
X break;
X }
X
X close(fd);
X
X return s;
}
X
/
mess to scan mountpoints and umount concerned paths ****/
X
#include <dirent.h> /opendir/
#include <sys/dcmd_blk.h> /*DCMD_FSYS
/
#include <sys/statvfs.h> /fstatvfs/
#include <sys/mount.h> /umount/
#include <malloc.h> /malloc/
#include <limits.h> /PATH_MAX/
#include <stdlib.h> /realpath/
#include <sys/netmgr.h> /netmgr_ndtostr()/
X
enum scan_devdir { NO_SUBPARTITION=1, SCAN_DEVDIR=0 };
/
NO_SUBPARTITION will supress scanning into /dev/,
X * which will only needed when finding subpartititons (/dev/hd0t77 in /dev/hd0),
X * but unlikely for ejectable media (/dev/cd0).
X * (whatabout zip disks?)
X /
X
/
device/mountpoint pairs gleaned from /proc/mount/ */
struct mtchain_t {
X char *mountpoint;
X char *device;
X int dev_mode;
X char mt_type[16];
X struct mtchain_t *next;
X struct mtchain_t parent;
} _mtchain = {
X .mountpoint = NULL, .device = NULL, .next = NULL, .parent = NULL
X /
if member changes, also change the member filling in scan_mount1() */
},
X mtchain = &_mtchain; / always use mtchain->next for initializer /
/
try to get rid of mtarray:
X instead of parent having ->sub, let children have ->parent.
X Scanning whole list then should be normal traversal of mtchain->next /
/
this may need to full-scan the list to get the dependency
X children, which is inefficient */
X
struct {
X int len;
X char path;
} qlocal = {0,NULL}; /
/net// /
X
void
umountall(const char devpath)
{
X char path[PATH_MAX] = “/proc/mount”; /
strlen==11 magic
/
X static void scan_mount1(char *procmountpath, enum scan_devdir scflag);
X static void reparent(void);
X static void umount_sub(const struct mtchain_t *mt);
X struct mtchain_t mtp;
X
X /
build linear linked dev/mount list /
X scan_mount1(path, NO_SUBPARTITION);
X reparent();
X
X /
Extract the /net/ string, if any. /
X /
(reuse path[]) /
X if (0 < netmgr_ndtostr(ND2S_DIR_SHOW, ND_LOCAL_NODE, path, sizeof(path))) {
X qlocal.len = strlen(path);
X qlocal.path = strdup(path); /XXX needs free()/
X } else {
X qlocal.len = 1;
X qlocal.path = strdup("/");
X }
X /
Resolve intermediate symlinks of passed devpath, and
X * canonicalize /net/myhost/ to /net/myhost.fq.dn/, if any. /
X /
(NOTE: with /link → /abs/path symlink, /net/myhost/link
X * could resolve to non-qnet /abs/path ) /
X /
(reuse path) /
X realpath(devpath, path);
X
X /
Convert the devpath to qnet string, otherwise
X * “/dev/cd0” won’t match “/net//dev/cd0” extracted from
X * mount info when qnet is active.
X /
X if (!strncmp(path, qlocal.path, qlocal.len)) {
X /
devpath is already qnet path /
X } else {
X /
prepend qlocal: “/dev/cd0” to “/net//dev/cd0” /
X /
XXX TODO: non-local qnet path /
X /
(assume realpath resolves to “/…”, having / at head) /
X int n = qlocal.len;
X if (qlocal.path[qlocal.len-1] == ‘/’) n–;
X memmove(path+n, path, strlen(path)+1); /XXX overflow/
X strncpy(path, qlocal.path, n);
X }
X
X /
now path will be “/net//dev/cd0” /
X if (G.verbose>=2) { printf(“Device resolved to %s\n”, path); }
X
X /
find the tree trunk matching the device path, and
X * umount it /
X for (mtp=mtchain->next; mtp; mtp=mtp->next) {
X if (!strcmp(path, mtp->device)) {
X umount_sub(mtp);
X break;
X }
X }
X
X /
cleanup; not necessary? /
X while (NULL != mtchain->next) {
X mtp = mtchain->next;
X mtchain->next = mtp->next; /
detach mtp /
X free(mtp->mountpoint);
X free(mtp->device);
X free(mtp);
X }
X /
if (qlocal.path) { free(qlocal.path); qlocal.path=NULL; } /
}
X
/
Recursively descend /proc/mount and build dev/mountpoint pairs /
/
Chain the found mountpoints into mtchain->next list */
static void
scan_mount1(/volatile/ char procmountpath[], enum scan_devdir scflag)
{
X DIR *dir;
X struct dirent de;
X char p;
X
X dir = opendir(procmountpath);
X if (!dir) return;
X p = procmountpath + strlen(procmountpath); /
tail /
X
X while ((de = readdir(dir))) {
X int a1,a2,a3,a4,a5;
X int fd;
X struct stat statbuf;
X
X p = ‘/’;
X strcpy(p+1,de->d_name); /
procmountpath="/proc/mount/1234" /
X
X if (stat(procmountpath, &statbuf) < 0) continue;
X
X /
,,,, /
X /
if the directory has this entry, the directory
X * is attached by some resmgr /
X if (5 != sscanf(de->d_name, “%d,%d,%d,%d,%d”, &a1,&a2,&a3,&a4,&a5)) {
X /
normal-ish entry /
X if (S_ISDIR(statbuf.st_mode)) {
X if (scflag==NO_SUBPARTITION &&
X !strcmp(procmountpath+11/magic/,"/dev"))
X continue;
X /recurse/
X scan_mount1(procmountpath, scflag);
X }
X continue;
X }
X if (a5 != 0) continue;
X /
,,
,
,0 /
X if (0 <= (fd = open(procmountpath, O_RDONLY)) ) {
X char buf[256];
X struct statvfs vfs; /
use fstatvfs() to determine mountpointness */
X if (0 <= fstatvfs(fd, &vfs) &&
X 0 <= devctl(fd, DCMD_FSYS_MOUNTED_ON, buf, sizeof(buf), NULL) ) {
X struct mtchain_t mt = malloc(sizeof(struct mtchain_t));
X /
fill *mt in this block /
X
X mt->device = strdup(buf);
X
X devctl(fd, DCMD_FSYS_MOUNTED_AT, buf,sizeof(buf), NULL);
X mt->mountpoint = strdup(buf);
X
X stat(mt->device, &statbuf);
X mt->dev_mode = statbuf.st_mode;
X
X strcpy(mt->mt_type, vfs.f_basetype);
X
X if (G.verbose>=2) printf("%s on %s at %s -t %s\n",
X procmountpath+11/magic/,
X mt->device, mt->mountpoint, mt->mt_type);
X /
chain the found mountpoint into list */
X mt->next = mtchain->next;
X mtchain->next = mt;
X mt->parent = NULL;
X }
X close(fd);
X }/open/
X }/readdir/
X p = ‘\0’;
X closedir(dir);
X return;
}
X
/
Rearrange the mtchain->next list according to mount dependencies /
/

X * dev /dev/cd0 at /fs/cd0
X * is ->parent of
X * dev /fs/cd0/boot/fs/qnxbase.qfs at /pkgs/base
X * dev /fs/cd0/boot/fs/qnxpub100.qfs at /fs/qnxpub100
X */
static void
reparent(void)
{
X struct mtchain_t mt;
X
X for (mt=mtchain->next; mt; mt=mt->next) {
X struct mtchain_t ps, candidate;
X int maxmatchlen = 0;
X
X /
block device stands for itself;
X * don’t reparent them under “/” /
X if (S_ISBLK(mt->dev_mode)) continue;
X if (!strcmp(mt->mt_type, “pkg”)) continue;
X
X /
scan for a longest parent mountpoint (/fs/cd0)
X * which mt->device (/fs/cd0/boot/a.qfs) belongs /
X candidate = NULL;
X for (ps=mtchain->next; ps; ps=ps->next) {
X int pmlen = strlen(ps->mountpoint);
X if (!strncmp(ps->mountpoint, mt->device, pmlen) &&
X maxmatchlen < pmlen &&
X (mt->device[pmlen]==’\0’ || /exact/
X ps->mountpoint[pmlen-1]==’/’ || /
/fs/cd0/ match /fs/cd0/boot… /
X mt->device[pmlen]==’/’ /
/fs/cd0 /fs/cd0/boot
/ )) {
X candidate = ps;
X maxmatchlen = pmlen;
X }
X }
X if (!candidate) {
X fprintf(stderr, “Warning: dangling device %s at %s\n”, mt->device, mt->mountpoint);
X continue;
X }
X if (candidate==mt) continue; /
only matched myself /
X /
have parent /
X mt->parent = candidate;
X if (G.verbose>=2) printf("(%s at %s) have (%s at %s)\n",
X candidate->device, candidate->mountpoint,
X mt->device, mt->mountpoint);
X }
}
X
/
umount things below mt, then umount itself */
static void
umount_sub(const struct mtchain_t *mt)
{
X const struct mtchain_t sub;
X
X /
unmount things below here /
X if (G.verbose>=1)
X printf(“Checking mounted device below %s\n”, mt->mountpoint);
X
X /
scan for children: full scan of those who has ->parent==this; inefficient /
X for (sub=mtchain->next; sub; sub=sub->next) {
X if (sub->parent != mt) continue;
X
X if (G.verbose>=1)
X printf("%s on %s is within %s\n",
X sub->device, sub->mountpoint, mt->mountpoint);
X umount_sub(sub); /recurse/
X }
X if (G.verbose>=0) {
X if (G.verbose>=0) printf(“umount %s”, mt->mountpoint);
X if (G.verbose>=1) printf("\t# device %s", mt->device);
X putchar(’\n’);
X }
X if (!G.dryrun) umount(mt->mountpoint, G.umount_flags);
}
X
/
TODO:
X * - eject /net/other.host/…
X * - better verbose diag, deleting /net/local.host
X */
SHAR_EOF
(set 20 03 10 15 05 24 36 ‘eject/eject.c’; eval “$shar_touch”) &&
chmod 0664 ‘eject/eject.c’ ||
$echo ‘restore of’ ‘eject/eject.c’ ‘failed’
shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'eject/eject.c'"
test 11512 -eq “$shar_count” ||
$echo ‘eject/eject.c:’ ‘original size’ ‘11512,’ ‘current size’ “$shar_count!”
fi

============= eject/eject.qpg ==============

if test -f ‘eject/eject.qpg’ && test “$first_param” != -c; then
$echo ‘x -’ SKIPPING ‘eject/eject.qpg’ ‘(file already exists)’
else
$echo ‘x -’ extracting ‘eject/eject.qpg’ ‘(text)’
sed ‘s/^X//’ << ‘SHAR_EOF’ > ‘eject/eject.qpg’ &&
QPG:Generation
X QPG:Options
X <QPG:User unattended=“no” verbosity=“2” listfiles=“yes”/>
X QPG:Build</QPG:Build>
X <QPG:Repository generate=“yes”/>
X <QPG:FinalDir dir="." />
X </QPG:Options>
X
X QPG:Values
X QPG:Files
X <QPG:Add file="./eject" install="/opt/bin/"/>
X <QPG:Add file="./eject.c" install="/opt/share/src/eject/" component=“src” />
X <QPG:Add file="./eject.qpg" install="/opt/share/src/eject/" component=“src” />
X <QPG:Add file="./Makefile" install="/opt/share/src/eject/" component=“src” />
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:ProductNameeject</QPM:ProductName>
X QPM:ProductIdentifiereject</QPM:ProductIdentifier>
X QPM:VendorNamePublic</QPM:VendorName>
X QPM:VendorInstallNamevega</QPM:VendorInstallName>
X QPM:VendorEmail</QPM:VendorEmail>
X QPM:AuthorNameTaketo Kabe</QPM:AuthorName>
X QPM:AuthorURL</QPM:AuthorURL>
X QPM:AuthorEmailkabe@sra-tohoku.co.jp</QPM:AuthorEmail>
X QPM:ProductDescriptionShorteject & umount removable device</QPM:ProductDescriptionShort>
X QPM:ProductDescriptionLong
eject will eject/load a CDROM tray or similar removable device.
X It will also umount any corresponding filesystems, including files
mounted as a filesystem.
X </QPM:ProductDescriptionLong>
X </QPM:ProductDescription>
X
X QPM:ReleaseDescription
X QPM:ReleaseVersion1.1</QPM:ReleaseVersion>
X QPM:ReleaseUrgencyMedium</QPM:ReleaseUrgency>
X QPM:ReleaseStabilityStable</QPM:ReleaseStability>
X QPM:ReleaseNoteMinor

  • Fix fatal error on “eject -”.
  • “eject filename.img” will now work.
    </QPM:ReleaseNoteMinor>
    X QPM:ReleaseNoteMajor</QPM:ReleaseNoteMajor>
    X QPM:ReleaseCopyrightBSD License</QPM:ReleaseCopyright>
    X </QPM:ReleaseDescription>
    X
    X QPM:ContentDescription
    X <QPM:ContentTopic xmlmultiple=“true”>System/Device Specific/Storage System</QPM:ContentTopic>
    X QPM:ContentKeywordeject, unmount, umount, CDROM</QPM:ContentKeyword>
    X QPM:TargetOSqnx6</QPM:TargetOS>
    X QPM:HostOSnone</QPM:HostOS>
    X <QPM:DisplayEnvironment xmlmultiple=“true”>Console</QPM:DisplayEnvironment>
    X <QPM:TargetAudience xmlmultiple=“true”>User</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
    X <QPG:PackageFilter component=“src”>
    X QPM:PackageManifest
    X QPM:ProductDescription
    X QPM:ProductDescriptionLong
    Source code for the eject utility.
    X </QPM:ProductDescriptionLong>
    X </QPM:ProductDescription>
    X </QPM:PackageManifest>
    X </QPG:PackageFilter>
    X </QPG:Values>
    </QPG:Generation>
    SHAR_EOF
    (set 20 03 10 21 01 59 54 ‘eject/eject.qpg’; eval “$shar_touch”) &&
    chmod 0664 ‘eject/eject.qpg’ ||
    $echo ‘restore of’ ‘eject/eject.qpg’ ‘failed’
    shar_count="LC_ALL= LC_CTYPE= LANG= wc -c < 'eject/eject.qpg'"
    test 3971 -eq “$shar_count” ||
    $echo ‘eject/eject.qpg:’ ‘original size’ ‘3971,’ ‘current size’ “$shar_count!”
    fi
    rm -fr _sh08011
    exit 0