#!/bin/sh
#
# NAME: cdfs
#
# Revision: 1.0
#
# Modification history:
#	000	banta	10/1/90
#		creation
#						(Bill Petro)
#
#       Copyright (c) 1988-91, Sun Microsystems, Inc.  All Rights Reserved.
#       Sun considers its source code as an unpublished, proprietary
#       trade secret, and it is available only under strict license
#       provisions.  This copyright notice is placed here only to protect
#       Sun in the event the source is deemed a published work.  Dissassembly,
#       decompilation, or other means of reducing the object code to human
#       readable form is prohibited by the license agreement under which
#       this code is provided to the user or company in possession of this
#       copy.
#
#       RESTRICTED RIGHTS LEGEND: Use, duplication, or disclosure by the
#       government is subject to restrictions as set forth in subparagraph
#       (c)(1)(ii) of the Rights in Technical Data and Computer Software 
#       clause at DFARS 252.227-7013 (October 1988) and FAR 52.227-19 
#       (June 1987).
#
# DISCLAIMER:
# 
# 	This script is not supported and is provided to you AS IS for use 
# 	at your own risk.  NEITHER SUN MICROSYSTEMS, INC. NOR SUNSOFT, INC.
# 	MAKE ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT LIMITED TO,
# 	THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
# 	NEITHER SUN MICROSYSTEMS, INC. NOR SUNSOFT, INC. SHALL BE LIABLE FOR
# 	ANY DAMAGES INCLUDING, BUT NOT LIMITED TO, CONSEQUENTIAL DAMAGES.
# 
# DESCRIPTION:
#
#	This script will take a clean directory structure, and create a
#	UFS filesystem image suitable for transfer to a CD-ROM.  This script
#	expects either no arguments or 3 or 4 arguments.  If no arguments
#	are present, all options will be prompted.  If arguments are present,
#	the first argument is the path of the root of a directory structure
#	to be transferred to CD-ROM; the second is a character device node
#	of a disk partition to be used to making the filesystem.  The third
#	is the destination of the filesystem image.  An optional fourth 
#	argument is a blocking factor for creating the image, if the image is
#	put on tape.
#
# USAGE:
# 	cdfs [ source partition image [blocking] ]
#
#    source - root of the source directory structure
#    rpart - character device node of disk partition
#    image - name of the target image file for this operation
#    blocking factor - block size for transfer to image, used if
#                      image destination is a tape
#
# You must be root to run this script.
#

TRUE=0
FALSE=1
OK=0
FAIL=1
PATH=/usr/bin:/usr/ucb:/bin:/etc
tmpmnt=/tmp/cdm$$

trap "cleanup $FAIL" 0 1 2

#
# function: cleanup()
#
# arguments: condition to exit with
#
# clean up when we're done and exit with ok or failure condition
# i.e. umount stuff, rm temp files and directories
#
cleanup ()
{
    umount $tmpmnt 2> /dev/null
    rmdir $tmpmnt 2> /dev/null
#    rm -f /tmp/cdfs*$$
    exit $1
}

#
# function: getyn()
#
# arguments: prompt for question
#
# prompt for a y/n response, return TRUE for a 'y' answer, FALSE for
# 'n'
#
getyn()
{
    pline="$* (y or n):"
    while echo -n $pline
    do
        read ans
	case $ans in
	    "y" | "Y") return $TRUE ;;
	    "n" | "N") return $FALSE ;;
	    *) echo "Please enter Y or N." ;;
        esac
    done
}

#
# function: prompt()
#
# arguments: prompt for question
#
# Prompt for response to a question.  Response is returned in $cmd
# If response is "q", a failure result is returned.  In all other
# cases, true is returned.
# -x sets -x for the shell for debugging purposes
# +x turns it off
#
prompt ()
{
    pline="$* or q to quit: "
    while echo -n $pline
    do
	read cmd
	case $cmd in
	    "q") return $FALSE ;;
	    "") echo "Please enter a response."
	        continue ;;
	    "-x") set -x;  continue;;
	    "+x") set +x;  continue;;
	    *) return $TRUE ;;
        esac
    done
}

#
# function: mountfs()
#
# arguments: partition to mount
#
# Create a mount point and mount the specified partition on on it.
# Fails if the mount point can't be created or if the mount is
# unsuccessful
#
mountfs ()
{
    _dpart=$1

# create a place to mount the filesystem
    [ -d $tmpmnt ] || {
	mkdir $tmpmnt 2>/dev/null || {
	   echo "
$progname: Couldn't create mount point for filesystem.
"
	   return $FAIL
	}
    }

# mount that puppy
    mount $_dpart $tmpmnt || {
	echo "
$progname: Unable to mount $_dpart on $tmpmnt
"
	return $FAIL
    }

# determine the amount of usable space on the partition
    avail_sz=`df|grep $_dpart|awk '{print $4}'`
    return $OK
}

#
# function: getsize
#
# arguments: directory structure to snag size of
#
# cd to the specified directory and do a du to get the size
# Fails if we cannot cd to the directory
#
getsize ()
{
    _curdir=`pwd`

# make sure we can really cd to the src directory.  We only check once, though
    cd $1 2> /dev/null
    [ `pwd` = "$1" ] || {
	echo "
$progname: couldn't cd to $1
"
	cd $_curdir
	return $FAIL
    }

# get the size of the directory
    srcdir_sz=`du -s . | awk '{print $1}'`
    cd $_curdir
    return $OK
}

#
# function: makefs
#
# arguments: none
#
# make a filesystem on the specified partition.  failure when we can't
# dig up disk stats or making the fs fails horribly
#
makefs ()
{
    _curdir=`pwd`

# count the number of files.  This is close enough to an inode count for us
    cd $srcdir
    find . -print | sort > /tmp/cdfs1$$
    cd $_curdir
    inodecnt=`wc -l /tmp/cdfs1$$ | awk '{print $1}'`

# This is to make a squashed filesystem.  We choose a number of sectors
# that will hold the filesystem (increase it by 1/3 for UFS overhead)
# and use that as the filesystem size.  If you really want to use the
# entire partition, comment out the following lines of script
# down to the next comment below
# There should really be no reason you need to do this

    sectcnt=`expr $srcdir_sz / 3`
    sectcnt=`expr $sectcnt + $srcdir_sz`
    [ $sectcnt -gt $dpart_sz ] && {
         echo"
$progname: The partition $rdpart isn't large enough to hold
your directory structure.  Please choose a different disk partition."
         return $FAIL
    }
    dpart_sz=$sectcnt
# comment to here
    sectcnt=`expr $dpart_sz \* 2`

# get the number of bytes/inode needed
    bpi=`expr $dpart_sz \* 1024`
    bpi=`expr $bpi / $inodecnt`
# adjust it just a little so we are never under
    bpi=`expr $bpi \* 8`
    bpi=`expr $bpi / 10`

# pick a number around 200 for the number of cylinder groups, then adjust
# it so cylinders are all about the same size
    cpg=`expr $numcyl / 200`
    cpg=`expr $cpg + 1`
    cpg=`expr $numcyl / $cpg`

# make cpg a multiple of 16
    cpg=`expr $cpg / 16`
    cpg=`expr $cpg \* 16`

    echo "
Attempting to make the filesystem.
"

#
# we try to make a filesystem with "ideal" parameters.  Since the
# filesytem may not work exactly as we hope, we can try dropping the number
# cylinders per group.  We can't increase bytes per inode, beacuse we're
# trying to build the fewest number of inodes possible, anyway
#
#DEBUG    echo Command used: newfs -c $cpg -d 0 -i $bpi -s $sectcnt $rdpart

    until newfs -c $cpg -d 0 -i $bpi -s $sectcnt $rdpart | grep -v "restricts cylinders"
    do
        cpg=`expr $cpg - 16`
        [ $cpg -le 0 ] && {
	    echo "
$progname: Can't make a suitable filesystem based on 
filesystem size and directory structure.  You might try a different disk 
partition.
"
	    return $FAIL
        }

#DEBUG  echo Command used: newfs -c $cpg -d 0 -i $bpi -s $sectcnt $rdpart

    done

    echo "
Filesystem made.
"
    return $OK
}

#
# function: fillfs
#
# arguments: none
#
# this copies the directory structure to the new filesystem
#

fillfs ()
{

# lost+found is useless on a read-only fileystem
    rmdir $tmpmnt/lost+found

# check the space available again
# we made an attempt when we created the filesystem, but this is a 
# guaranteed check
    [ "$srcdir_sz" -ge "$avail_sz" ] && {
        echo "
$progname: the filesystem you created isn't large enough
to hold your directory structure.
"
        return $FAIL
    }

    _curdir=`pwd`
    cd $srcdir

# check for symbolic links.  This would make the new filesystem look
# ok on this system, but could cause problems when it's mounted elsewhere
    find . -type l -print > /tmp/linksfound 
    linkcnt=`wc -l /tmp/linksfound | awk '{print $1}'`
    [ "$linkcnt" -gt 0 ] && {
	echo "
$progname: Warning: $linkcnt symbolic links were found in your
directory structure.  Files may not be located the same place or may not
exist on the target system.  Make sure this is not a problem. A list of the
linked files can be found in /tmp/linksfound.
"
    }

# fill the filesystem
    echo "
Filling the new filesystem.  This may take a while.
"
    tar cf - . |(cd $tmpmnt; tar xf -)
    cd $_curdir
}

#
# function: makeimage
#
# arguments: none
#
# Make an image of the filesystem we just created and filled.
# Failure from this routine might be running out of space for your
# image, but I can think of no good way to trap this condition.
#
makeimage ()
{
    dd if=$rdpart of=$image $obsize count=$sectcnt
}

#
# function: testimage
#
# arguments: none
#
# check out the image we just made.  This will fail if mounting the
# filesystem fails or the files contained on the filesystem are
# not the same as the source directory
#
testimage()
{

# make sure the partition isn't already mounted
    df|grep $dpart > /dev/null && {
        echo "
$progname: The filesystem partition $dpart is currently mounted. 
Testing will not continue.
"
	return $FAIL
    }

# scrap the contents of the partition
    newfs $rdpart > /dev/null 2>&1

# if the image is on tape, give them a chance to get their media set
    [ -c "$image" ] && {
	echo "Ready to read your image."
	prompt "Make sure the image media is ready and type ok to continue" || {
	    echo "
$progname: Exiting at user request."
	    cleanup $OK
	}
    }

# copy the image back to the partition
    echo "
Checking out your filesystem.  This will take a while.
"
    dd if=$image of=$rdpart $ibsize

# try to mount the FS.  Failure here is a bad sign, with no understandable
# reason
    mountfs $dpart || {
	echo "
$progname: Your image failed to mount successfully.  Some part
of the creation process failed.  Please check your work and try again."
	return $FAIL
    }

# compare file lists from the two directories.  This should be a sufficient
# check of integrity.
    _curdir=`pwd`
    [ -f /tmp/cdfs1$$ ] || {
        cd $srcdir
        find . -print | sort > /tmp/cdfs1$$
    }
    cd $tmpmnt
    find . -print | sort > /tmp/cdfs2$$
    cd $_curdir

    [ -f /tmp/cdfs1$$ -a -f /tmp/cdfs2$$ ] || {
	echo "
$progname: Couldn't get a list of what is in the source directory
or the new filesystem."
	return $FAIL
    }

# compare the two lists.
    cmp /tmp/cdfs1$$ /tmp/cdfs2$$ || {
	echo "
$progname: The contents of your source directory and the CD-ROM
filesystem differ.  Some part of the creation process failed.  Please check
your work and try again."
        return $FAIL
    }

    echo "
Test of the filesystem image passed.  The image is now ready to
be put on CD-ROM"
    return $OK
}

#
# function: usage
#
# tell the user how to use this script
usage()
{
    echo "Usage: $progname [ srcdir partition image [ image-blocksize ] ]"
    cleanup $OK
}

#
# main()
#

progname=$0
iflag=
ibf=

#make sure we're root
id|grep id=0\(root\) > /dev/null || {
    echo "$progname: You must be superuser to run this utility."
    cleanup $FAIL
}

# grab the arguments, checking happens below
case $# in
    0) iflag=yes ;;
    1|2) usage ;;
    3|4)  srcdir=$1
	  rdpart=$2
	  image=$3
	  bsize=$4 ;;
    *) usage ;;
esac

# get the name of the source directory
[ "$iflag" = "yes" ] && {
    prompt "Enter the name of the source directory structure" || cleanup $OK
    srcdir=$cmd
}

#get the size of the source directory
getsize $srcdir || cleanup $FAIL

# no need to make an empty FS
[ $srcdir_sz = 0 ] && {
    echo "
$progname: The source directory you specified is empty.  Try again."
    cleanup $FAIL
}

dpflag=$iflag

# get the device node to use
while true
do
    [ "$dpflag" = "yes" ] && {
        prompt "Enter the full path name of a character device node of a usable disk
partition" || cleanup $OK
        rdpart=$cmd
   }

# did they give us a character device node?
    [ -c "$rdpart" ] || {
        echo "
$progname: $rdpart is not a character device node.
"
	dpflag=yes
        continue
    }

# grab a block device node based on what we know.
    rdbase=`basename $rdpart`
    dbase=`expr $rdbase : 'r\(.*\)'`
    dpart=`dirname $rdpart`
    dpart=$dpart/$dbase

# make sure that block device node exists
    [ -b "$dpart" ] || {
	echo "
$progname: Can't locate a block device node associated with $rdpart.
$rdpart is not a disk partition or no block device node exists for the
partition.  Select another partition or make sure a block node exists
for $rdpart.
"
	dpflag=yes
	continue
    }

# make sure that they didn't give us a currently mounted FS
    mount | grep $dpart > /dev/null && {
	echo "
$progname: $dpart is currently mounted as a filesystem.  It 
cannot be used for creating a CD-ROM filesystem.  Please select a different 
partition or unmount $dpart and start again.
"
	dpflag=yes
	continue
    }
    break
done

# while we have a good handle on the partition, get the number of sectors
# and cylinders
numsect=`dkinfo $dbase|tail -2|head -1|awk '{print $1}'`
numcyl=`dkinfo $dbase|tail -2|head -1|awk '{print $3}'`
numcyl=`expr $numcyl : '(\(.*\)'`
dpart_sz=`expr "$numsect" / 2`

# find out where to put the image
dpflag=$iflag

while true
do
    [ "$dpflag" = "yes" ] && {
        prompt "Enter where to put the filesystem image" || cleanup $OK
        image=$cmd
    }

# if this is a device, make sure we can write on it
    [ -c "$image" ] && {
       [ -w "$image" ] || {
	   echo "
$progname: You don't have write permission on the device 
$image.
"
	   dpflag="yes"
	   continue
       }
# get a block size for the device
       [ "$iflag" = "yes" ] && {
	  echo -n "Enter the block size for transfer [default: 512] :"
	  read $bsize
       }
       break
    }

# see if we can create an image in a regular file
    touch $image 2> /dev/null

    [ -f "$image" ] || {
	echo "
$progname: Can't create the filesystem image file $image.
"
	dpflag="yes"
	continue
    }

# there's a chance that image existed, so make sure we can write it
    [ -w "$image" ] || {
	echo "
$progname: No permission to write on image file $image.
"
	dpflag="yes"
	continue
    }
    break
done

# if the block size was set, make args to dd easy
[ -n "$bsize" ] && {
    ibsize="ibs=$bsize"
    obsize="obs=$bsize"
}


partletter=`expr $dbase : '.*\(.\)'`

case $partletter in
     a|b|c) echo "
Be careful when using the a, b, or c partition of a
disk drive.
" ;;
     *) ;;
esac

# make sure they really know what they're doing and can trash the partition
echo "
The next step will create a new filesystem on $rdpart.
This will destroy all data currently on this partition.
"

getyn "Is it ok to destroy the contents of $rdpart" || {
    echo "
$progname: Exiting at user's request."
    cleanup $OK
}

# make a filesystem
makefs $rdpart || {
    echo "
$progname: Attempt to create filesystem on $rdpart failed.
Please correct conditions listed above and try again."
    cleanup $FAIL
}

# mount the filesystem
mountfs $dpart || {
    echo "
$progname: Failed to mount $dpart. Filesystem may not be
good."
    cleanup $FAIL
}

# fill the filesystem
fillfs || {
    echo "
$progname: Failed to transfer $srcdir to filesystem partition.
Correct conditions above and try again."
    cleanup $FAIL
}

# unmount the filesystem
until umount $tmpmnt 2> /dev/null
do
     echo "
$progname: Failed to umount the filesystem.  Some other
process may be working inside the directory /tmp/cdm$$.\n"
    prompt "Correct the situation and type ok to continue" || cleanup $OK
done

# make an image of the fs
makeimage || {
    echo "
$progname: A filesystem image was not successfully made.
Please correct the problems listed above and try again."
    cleanup $FAIL
}

# see if they wanna test the image
getyn "Do you want to test this image" || {
    echo "
An image of the filesystem now exists in $image.  Test
this image before you try transferring it to CD-ROM.  Putting
an unusable filesystem image on CD-ROM is expensive and time 
consuming."
    cleanup $OK
}

# test the image
testimage || {
    echo "
Your image did not successfully check out.  Correct problems
and try again."
    cleanup $FAIL
}

cleanup $OK


