#!/bin/sh
# Portable version of "mkdirhier".
#
# This program uses the bare minimum of shell and external facilities,
# so that it will work even on ancient (c.1980) UNIX and UNIX-like
# systems.
#
#######################################################################
#
# Create a directory (with optional specified permissions and ownerships),
# and any needed parent directories.
#
# Permissions and ownerships of pre-existing parent directories is not
# altered: they are only applied to *created* directories.
#
# See USAGE below for command-line syntax.
#
# It is not an error for the directory to already exist.
#
# All sorts of wierd and wonderful directory pathnames are handled,
# even network paths specified with a leading double-slash.
#
# "permissions" specified with the -m flag are passed to the chmod
# program **without interpretation**.
# NOTE: portable "permissions" values are usually numeric, octal,
# and less than 01000 (octal).
# "user" specified with -u flag is passwd to chown without intepretation.
# "group" specified with -g flag is passwd to chgrp without intepretation.
#
#######################################################################
# Test this program against the following wierd directory-names:
#
#	/usr			/usr/tmp		/usr/tmp/.
#	/usr/./tmp		/			/.
#	/./etc/../var/tmp	a/b			./a/b	
#	a			./a			./a/../b
#	./a/../b/c		/a/b/c/./.././../d	./../.
#	.././..			//fred/usr/tmp		//fred/usr
#	//fred			/var/tmp/..x/y../a	...
#	/a/b/c/d/.../e		/a/b/c/d/...		../.../x/y
#
#######################################################################

# discover my short name and my real pathname...
#
ME="`echo $0 | sed -e 's:^.*/::'`"
case $0 in
	/*)	# invoked by absolute pathname
		REALME="$0"
		;;
	*)	# invoked by relative pathname
		# NOTE: must strip any leading "./" from $0...
		REALME="`pwd`/`echo $0 | sed -e 's:^\./::'`"
		if [ -x "$REALME" ] && [ -f "$REALME" ] ; then
			echo "" >/dev/null
		else
			# Hmmm, wierd parent shell on this system sets
			# argument zero incorrectly, so have to trust
			# $PATH and hope for the best...
			REALME="$0"
		fi
		;;
esac

# parse flags:
#
usr=''
grp=''
mode=''
USAGE="$ME [ -m permissions ] pathname"
while [ "true" = "true" ] ; do
	case $1 in
		-m)	shift
			if [ -z "$1" ] ; then
				echo "$USAGE" 1>&2
				exit 1
			fi
			mode="$1"
			shift
			;;
		-m*)	mode="`echo $1 | sed -e 's/-m//'`"
			shift
			;;
		-g)	shift
			if [ -z "$1" ] ; then
				echo "$USAGE" 1>&2
				exit 1
			fi
			grp="$1"
			shift
			;;
		-g*)	grp="`echo $1 | sed -e 's/-g//'`"
			shift
			;;
		-u)	shift
			if [ -z "$1" ] ; then
				echo "$USAGE" 1>&2
				exit 1
			fi
			usr="$1"
			shift
			;;
		-u*)	usr="`echo $1 | sed -e 's/-u//'`"
			shift
			;;
		-*)	echo "$USAGE" 1>&2
			exit 1
			;;
		*)	break
			;;
	esac
done


# just a quick check to make sure we actually have something to do!
#
if [ "X$1" = "X" ] || [ -d "$1" ] ; then
	exit 0
fi



# create the directory-tree...
# Uses depth-first upward head-recursion, to create any needed parents first...
#
# To ensure that only NEEDED parents are created, we must first convert
# the directory name into an absolute one with no relative components...
# However, be careful not to jumble directory names like "a/..b./.c../.../e"!
# Also, some systems use a leading "//" to invoke network filesystem semantics,
# which we must be careful not to circumvent (eg: by turning a leading
# double-slash into a single one when not at the top of the tree).
#

# make absolute:
case $1 in
	/*)	dir="$1" ;;
	*)	dir="`pwd`/$1" ;;
esac

# compress multiple slashes except for a leading double-slash...
prev=""
while [ "$prev" != "$dir" ] ; do
	prev="$dir"
	dir="`echo $dir | sed -e 's:\(..*\)//:\1/:g'`"
done

# the removal of "relative" sequences must be done in a loop, as
# removal of one kind may reveal another...
prev=""
while [ "$prev" != "$dir" ] ; do

	prev="$dir"

	# strip trailing slashes, unless we have only "/" or "//":
	dir="`echo $dir | sed -e 's:\(..*\)//*$:\1:g'`"

	# handle "/./" sequences:
	echo 1 > /dev/null
	dir="`echo $dir | sed -e 's:/\./:/:g'`"
	
	# strip trailing "/.":
	echo 2 > /dev/null
	dir="`echo $dir | sed -e 's:/\.$::g'`"
	
	# due to absoluteness, cannot have leading "./"
	
	# purge "xxx/../" sequences:
	echo 3 > /dev/null
	dir="`echo $dir | sed -e 's:[^/][^/]*/\.\./::g'`"
	
	# strip trailing "xxx/..":
	echo 4 > /dev/null
	dir="`echo $dir | sed -e 's:[^/][^/]*/\.\.$::g'`"
	
	# due to absoluteness, cannot have leading "../"
	
done


# the root directory is a slash-with-no-name, which is the
# only directory name that may get reduced to nothing.
[ "$dir" = "" ] && dir="/"

# We cannot create the root directory (it must always exist), and it has
# no parent, so just report success. Conveniently, this also stops the
# recursion when it reaches the top. We do not attempt to change permissions
# on the root directory, as that is likely to have arbitrarily bad
# side-effects...
[ "$dir" = "/" ] && exit 0

# now get name of parent directory, and recurse...
# Note: a pathname of "//xxx" must be mapped to the parent "/", because the
# the local root directory is the local parent of the (potential) network
# namespace...
#
parent="`echo $dir | sed -e 's:\(.*\)/[^/][^/]*$:\1:'`"
flags=""
[ -n "$usr" ] && flags="$flags -u $usr"
[ -n "$grp" ] && flags="$flags -g $grp"
[ -n "$mode" ] && flags="$flags -m $mode"
if "$REALME" $flags "$parent" && mkdir "$dir" ; then
	if [ "X$usr" != "X" ] ; then
		chown "$usr" "$dir" || exit 1
	fi
	if [ "X$grp" != "X" ] ; then
		chgrp "$grp" "$dir" || exit 1
	fi
	if [ "X$mode" != "X" ] ; then
		chmod "$mode" "$dir" || exit 1
	fi
	exit 0
else
	exit 1
fi
