#!/bin/sh

# Copyright (C) 2005, 2006, 2007 Junjiro Okajima
#
# This program, aufs is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

# $Id: unionctl,v 1.1 2007/06/04 02:18:19 sfjro Exp $

# simple script for controling aufs.
# compatible with unionctl(8) in unionfs(5).
# if you use "mount -o remount" to add/del/mod branches,
# this scirpt is unnecessary.

PATH=/usr/bin:/usr/sbin:/bin:/sbin
export PATH
eecho() { echo "$@" 1>&2; }

me=`basename $0`
err=0
usage()
{
	eecho "${me}: $@"
	eecho usage:
	eecho "${me}" union_dir --add \
		'[ --before | --after bindex | branch ]' '[ --mode perm ]' branch
	eecho "${me}" union_dir --remove branch
	eecho "${me}" union_dir --mode branch perm
	eecho "${me}" union_dir --list
	eecho "${me}" union_dir '--whereis | --query1 | --q1' path_under_union '[ ,,, ]'
	err=1
}

conv() # [escape]
{
	sed -r -e '
	s/\\/\\134/g
	s/$/\\012/
	' |
	tr -d '\n' |
	sed -r -e '
	s/ /\\040/g
	s/\t/\\011/g
	s/\r/\\015/g
	s/\\012$//
	' |
	{ test "$1" = "escape" && sed -r -e 's/\\/\\\\/g' || cat; }
	echo
}

tgt=
branches()
{
	echo "$tgt" |
	tr ',' '\n' |
	egrep '^(dirs|br)[=:]' |
	sed -r -e 's/^(dirs|br)[=:]//' |
	tr ':=' '\n '
}

########################################

test $# -lt 2 && eecho "${me}: bad arg $@" && exit 1
f=/proc/mounts
#f=/sys/fs/aufs/brs
test ! -f $f && eecho "${me}: $f is necessary" && exit 1

set -e
#set -x; echo $@
tmp=/tmp/$$
cd "$1"
mntpnt=`readlink -f "$PWD"`
e_mntpnt=`echo "$mntpnt" | conv escape`
cd "$OLDPWD"
action="$2"
shift 2

brs=/sys/fs/aufs/brs
if [ -r $brs ]
then
dev=`grep " $e_mntpnt " $brs | tail -n 1 | cut -f1 -d' '`
test ! "$dev" && eecho "illegal mount-point $mntpnt" && exit 1
br=`grep " $e_mntpnt " $brs | tail -n 1 | cut -f4 -d' '`
test -w $brs && >> $brs
test ! "$br" && eecho "illegal branches $br" && exit 1
echo "$br" | cut -f2- -d: | tr ':=' '\n ' > $tmp
else
tgt=`grep " $e_mntpnt aufs" $f | tail -n 1 | rev | cut -f3- -d' ' | rev`
test "$tgt" = "" && eecho "${me}: no such mntpnt $mntpnt" && exit 1
dev=`echo "$tgt" | sed -e 's: '"$mntpnt aufs"' .*$::'`
branches > $tmp
fi
#cat $tmp | while read i; do echo "$i"; done; #exit

find_bindex() # dir
{
	echo "$1" | grep -q '^[0-9]*$' && echo "$1" && return 0
	local i n err
	n=0
	cat $tmp | rev | cut -f2- -d' ' | rev |
	while read i
	do
		test "$i" = "$1" && echo $n && return 0
		n=`expr $n + 1`
	done
	err=$?
	test $err -eq 0 && return 0
	rm -f $tmp
	eecho "${me}: no such branch $1"
	echo -2
	return 1
}

Remount() #options...
{
	rm -f $tmp
	#local mount212p=0
	#{ mount --version 2> /dev/null || :; } | fgrep -q mount-2.12p && mount212p=1
	#test $mount212p -eq 1 && exec mount -o remount,"$@" "$mntpnt"
	#exec mount -o remount,"$@" "$dev" "$mntpnt"
	exec mount -o remount,"$@" "$mntpnt"
}

do_add()
{
	local bindex perm br
	bindex=0
	perm=rw # unionfs default
	br=
	while [ "$br" = "" ]
	do
		case "$1" in
		--before)	bindex=`find_bindex "$2"`
				;;
		--after)	bindex=`find_bindex "$2"`
				bindex=`expr $bindex + 1` || :
				;;
		--mode)		perm="$2";;
		--*)		eecho "${me}: unkown option $1"; return 1;;
		*)		br="$1"
		esac
		test "$br" = "" && shift 2 || :
	done

	test $# -ne 1 && eecho "${me}: bad arg $@" && return 1
	br="$1"
	test $bindex -lt 0 && bindex=0 || :
	Remount add:${bindex}:"${br}"="$perm"
}

do_del()
{
	test $# -ne 1 && eecho "${me}: bad arg $@" && return 1
	Remount del:"$1"
}

do_mod()
{
	test $# -ne 2 && eecho "${me}: bad arg $@" && return 1
	Remount mod:"$1"="$2"
}

do_list()
{
	local path mode
	cat $tmp | rev |
	while read mode path
	do
		mode=`echo $mode | rev`
		path=`echo "$path" | rev`
		case $mode in
		ro)	mode="r--";;
		rw)	mode="rw-";;
		ro+wh)	mode="R--";;
		*)	mode="---";;
		esac
		/bin/echo -e \\t"$path" \(${mode}\)
	done
}

do_query1()
{
	local path i f noexist
	cat $tmp | rev | cut -f2- -d' ' | rev > $tmp.path
	for i
	do
		while read path
		do
			# symlink is unsupported.
			set +e
			f=`readlink -f "$path/$i"`
			noexist=$?
			set -e
			test ! $noexist -eq 0 && continue
			if [ $# -eq 1 ]
			then
				echo "$path"
			else
				echo "$f"
			fi
			break
		done < $tmp.path
		err=$noexist
		test $noexist -eq 0 || return
	done
}

case "$action" in
--add)		do_add "$@";;
--remove)	do_del "$@";;
--mode)		do_mod "$@";;
--list)		do_list;;
--q1|--query1|--whereis)
		do_query1 "$@";;
--query)	usage "unsupported action $action"; err=1;;
*)		usage "unkown action $action"; err=1;;
esac

rm -f $tmp $tmp.*
exit $err
