#!/bin/sh -efu

. girar-sh-functions
. shell-quote
PROG='girar-task add'

usage()
{
	[ -z "$*" ] || message "$*"
	cat >&2 <<EOF
Usage: $PROG [<task_id> [<before_subtask_id>]] repo <gear_repo> <gear_tag>
   or: $PROG [<task_id> [<before_subtask_id>]] branch <gear_repo> [<gear_branch>]
   or: $PROG [<task_id> [<before_subtask_id>]] kmod <template_repo> <kernel_flavour> <module_name>
   or: $PROG [<task_id> [<before_subtask_id>]] srpm <srpm file>
   or: $PROG [<task_id> [<before_subtask_id>]] del <package>
   or: $PROG [<task_id> [<before_subtask_id>]] rebuild <package>
   or: $PROG [<task_id> [<before_subtask_id>]] copy <package> [<binary_repository_name>]
   or: $PROG [<task_id>] kmodules <kflavour>
   or: $PROG [<task_id>] genbases
EOF
	exit 1
}

if [ "${1-}" = '--help' ]; then
	usage
fi

[ "$#" -le 6 ] || usage 'Too many arguments.'
[ "$#" -ge 1 ] || usage 'Not enough arguments.'

id=
subtask_id=
case "$1" in
	[1-9]*)	id=$(PROG="$PROG" girar-task-find-current "$1"); shift
		case "$1" in
			repo|branch|genbases|kmod|srpm|del|rebuild|kmodules|copy) ;;
			[1-7]*) subtask_id="$1"; shift ;;
			*) usage "Invalid action: $1" ;;
		esac ;;
	*)	id=$(PROG="$PROG" girar-task-find-current) ;;
esac

[ "$#" -le 4 ] || usage 'Too many arguments.'
[ "$#" -ge 1 ] || usage 'Not enough arguments.'

action="$1"; shift

enable -f /usr/lib/bash/lockf lockf

dir=
validate_repo_dir()
{
	local dir0="$dir"

	cd
	dir="$(printf %s "$dir" |tr -s /)"
	dir="$(prefix_packages "$dir")"
	dir="$(add_git_suffix "$dir")"
	[ "${dir#/}" != "$dir" ] ||
		dir="$GIRAR_HOME/$GIRAR_USER/$dir"
	[ "${dir#$GIRAR_HOME/}" != "$dir" -o "${dir#/projects/}" != "$dir" ] &&
	printf '%s' "$dir" |egrep -qs \
			-e "^$GIRAR_HOME/"'[a-z][a-z_0-9]+/packages/[A-Za-z0-9][-A-Za-z0-9_.]+[.]git$' \
			-e '^/projects/([^/]*\/)*[A-Za-z0-9][-A-Za-z0-9_.]+[.]git$' ||
		fatal "$dir0: path to git repository does not belong to allowed directory tree"

	[ -d "$dir" ] ||
		fatal "$dir0: directory not available"

	cd "$dir"
	# obtain a shared lock on the source git repository.
	builtin lockf -s -v .
	dir="$PWD"
}

validate_tag_name()
{
	git rev-parse --symbolic --tags |fgrep -xqse "$tag_name" ||
		fatal "$tag_name: tag name not found"
	tag_id="$(git rev-parse --verify "$tag_name")" ||
		fatal "$tag_name: tag verification failure"
	[ "$tag_name" = "$(git cat-file tag "$tag_id" | sed -n '/^tag /s///p')" ] ||
		fatal "$tag_name: tag name verification failure"
	if ! sig_text="$(GNUPGHOME=/usr/lib/alt-gpgkeys git verify-tag "$tag_id" 2>&1)" && ! sig_text="$(GNUPGHOME=/usr/lib/etersoft-gpgkeys git verify-tag "$tag_id" 2>&1)" && ! sig_text="$(GNUPGHOME=/usr/lib/girar-gpgkeys git verify-tag "$tag_id" 2>&1)"; then
		printf >&2 '%s\n' "$sig_text"
		fatal "$tag_name: tag signature verification failure"
	fi

	fpr="$(printf %s "$sig_text" |
		sed '/^gpg: Signature made .* using .* key ID */!d;s///;q')"
	[ -n "$fpr" ] ||
		fatal "$tag_name: gpg fingerprint not found"

	tag_author="$(GNUPGHOME=/usr/lib/alt-gpgkeys gpg --list-keys "$fpr" 2>/dev/null |
		sed '/^uid[[:space:]]\+/!d;s///;q')"
	[ -n "$tag_author" ] ||
	tag_author="$(GNUPGHOME=/usr/lib/etersoft-gpgkeys gpg --list-keys "$fpr" 2>/dev/null |
		sed '/^uid[[:space:]]\+/!d;s///;q')"
	[ -n "$tag_author" ] ||
	tag_author="$(GNUPGHOME=/usr/lib/girar-gpgkeys gpg --list-keys "$fpr" 2>/dev/null |
		sed '/^uid[[:space:]]\+/!d;s///;q')"
	[ -n "$tag_author" ] ||
		fatal "$tag_name: gpg uid not found"
	# normalize email domain
	local qemail_domain qemail_fqdn
	quote_sed_regexp_variable qemail_domain "${EMAIL_DOMAIN%.*}"
	quote_sed_regexp_variable qemail_fqdn "$EMAIL_DOMAIN"
	tag_author="$(printf %s "$tag_author" |
		LANG=C sed 's/@'"$qemail_domain"'\.[a-z]\+>/@'"$qemail_fqdn"'>/')"

	local userid
	userid="$(printf %s "$tag_author" |
		LANG=C sed -n 's/^[^<]\+<[[:space:]]*\([a-z][a-z0-9_-]\+\)\([[:space:]]*@\|[[:space:]]\+at[[:space:]]\+\).*$/\1/p' |
		tr '[:upper:]' '[:lower:]' |tr - _)"
	[ "$#" -gt 0 ] &&
		userid="$1"
	[ -n "$userid" -a -d "$GIRAR_PEOPLE_QUEUE/$userid" ] ||
		fatal "$tag_name: unacceptable signature: login name \`$userid' not found"
}

validate_rebuild_dir()
{
	local sym id

	sym="$(printf %s "$package" |cut -c1)"
	dir="/gears/$sym/$package.git"
	[ -d "$dir" ] ||
		fatal "$dir: directory not available"

	cd "$dir"
	# obtain a shared lock on the source git repository.
	builtin lockf -s -v .
	dir="$PWD"

	id="$(git rev-parse --branches="[${repo:0:1}]${repo:1}")"
	[ -n "$id" ] ||
		fatal "$dir: branch \"$repo\" not available"
	tag_name="$(git describe --exact-match "$id")"
	validate_tag_name
}

validate_branch_name()
{
	git rev-parse --symbolic --branches |fgrep -xqse "$branch_name" ||
		fatal "$branch_name: branch name not found"

	branch_id="$(git rev-parse --verify "$branch_name")" ||
		fatal "$branch_name: branch verification failure"
}

validate_template_branch()
{
	cd "$dir"
	branch_name="$template_branch"
	validate_branch_name
	cd -
}

srpm_tmp_locked=
nevr=
validate_srpm()
{
	[ -n "${srpm##*/*}" -a "$srpm" != "${srpm%.src.rpm}" ] ||
		fatal "$srpm: Invalid path"
	pushd "$HOME/incoming" >/dev/null
	[ -f "$srpm" ] ||
		fatal "$srpm: File not found"

	mkdir -p tmp
	if [ -z "$srpm_tmp_locked" ]; then
		# obtain an exclusive lock on the srpm tmp directory.
		builtin lockf -v tmp
		srpm_tmp_locked=1
	fi
	mv -- "$srpm" tmp/
	cd tmp

	nevr="$(rpmquery -p --qf '%{name}\t%|epoch?{%{epoch}:}|%{version}-%{release}' -- "$srpm")" ||
		fatal "$srpm: rpmquery failed"
	package="${nevr%	*}"
	GB_REPO_NAME="$repo" sisyphus_check --files -- "$srpm" ||
		fatal "$srpm: sisyphus_check failed"
	girar-check-nevr-in-repo "$package" "${nevr#*	}" "$repo" ||
		fatal "$srpm: too old for \`$repo'"

	popd >/dev/null
}

handle_kmodules()
{
	local kimage qkflavour
	kimage="kernel-image-$kflavour"

	girar-check-package-in-repo "$kimage" "$repo" ||
		fatal "package \`$kimage' not found in \`$repo'"

	repo="$(girar-normalize-repo-name "$repo")"
	local conf
	conf="$GIRAR_REPO_CONF_DIR/$repo"
	if [ -s "$conf" ]; then
		. "$conf"
	fi
	[ -d "${GIRAR_REPO_DIR-}" ] ||
		fatal 'GIRAR_REPO_DIR is either undefined or invalid'

	cd "$GIRAR_REPO_DIR/files/list"

	quote_sed_regexp_variable qkflavour "$kflavour"
	set -- $(sed -n "s/^\\(kernel-modules-[^[:space:]]\\+-$qkflavour\\)[[:space:]].*/\1/p" src.list)
	for m; do
		girar-task-add "$id" rebuild "$m"
	done
	echo >&2 "task #$id: added $# kernel modules for \`$kimage'"
}

case "$action" in
	repo)
	[ "$#" -eq 2 ] || usage 'Not enough arguments.'
	dir="$1"; shift
	tag_name="$1"; shift
	;;
	branch)
	[ "$#" -eq 1 -o "$#" -eq 2 ] || usage 'Not enough arguments.'
	dir="$1"; shift
	validate_repo_dir
	branch_name="master"
	if [ "$#" -eq 1 ]; then
		branch_name="$1"; shift
	fi
	validate_branch_name
	;;
	kmod)
	[ "$#" -eq 3 ] || usage 'Not enough arguments.'
	dir="$1"; shift
	validate_repo_dir
	flavour="$1"; shift
	module="$1"; shift
	kernel_name="kernel-image-$flavour"
	module_name="kernel-modules-$module-$flavour"
	;;
	del|rebuild)
	[ "$#" -le 1 ] || usage 'Too many arguments.'
	[ "$#" -ge 1 ] || usage 'Not enough arguments.'
	package="$1"; shift
	;;
	kmodules)
	[ "$#" -le 1 ] || usage 'Too many arguments.'
	[ "$#" -ge 1 ] || usage 'Not enough arguments.'
	[ -z "$subtask_id" ] ||
		usage "subtask #$subtask_id is not supported for \`$action' action"
	kflavour="$1"; shift
	;;
	copy)
	[ "$#" -ge 1 ] || usage 'Not enough arguments.'
	package="$1"; shift
	copy_repo=
	copy_pocket=
	if [ $# -ge 1 ]; then
		if [ $# -ge 2 ]; then
			[ "$1" = "-p" ] || usage "Bad repository argument."
			shift; copy_pocket="$(validate_pocket_name "$1")"; shift
			[ -n "$copy_pocket" ] || usage "Empty pocket name."
			copy_repo="$(girar-get-pocket-repo $copy_pocket)"
			copy_repo_name="-p $copy_pocket"
		else
			copy_repo="$1"; shift
			copy_repo_name="$copy_repo"
		fi
	fi
	[ -n "$copy_repo" ] || copy_repo=sisyphus
	copy_repo="$(girar-normalize-repo-name "$copy_repo")"
	;;
	srpm)
	[ "$#" -le 1 ] || usage 'Too many arguments.'
	[ "$#" -ge 1 ] || usage 'Not enough arguments.'
	srpm="$1"; shift
	;;
	genbases)
	[ "$#" -le 0 ] || usage 'Too many arguments.'
	[ "$#" -ge 0 ] || usage 'Not enough arguments.'
	;;
	*) usage "Invalid action: $action" ;;
esac

cd "$GB_TASKS/$id"
if [ "${GIRAR_TASK_LOCKED-}" != "$id" ]; then
	# obtain an exclusive lock on the TASKS structure
	builtin lockf -n . ||
		fatal "task #$id is locked"
	export GIRAR_TASK_LOCKED="$id"
fi

repo="$(cat task/repo)"
repo_name="$repo"
pocket=
if [ -f task/pocket ]; then
    pocket="$(cat task/pocket)"
    repo_name="-p $pocket"
fi

check_package_in_all_repos()
{
	local name="$1"
	if [ -z "$pocket" ]; then
		girar-check-package-in-repo "$name" "$repo"
	else
		girar-check-package-in-pocket "$name" "$pocket" ||
			girar-check-package-in-repo "$name" "$repo"
	fi
}

check_package_in_repo()
{
	local name="$1"
	if [ -z "$pocket" ]; then
		girar-check-package-in-repo "$name" "$repo"
	else
		girar-check-package-in-pocket "$name" "$pocket"
	fi
}

check_package_in_copy_repo()
{
	local name="$1"
	if [ -z "$copy_pocket" ]; then
		girar-check-package-in-repo "$name" "$copy_repo"
	else
		girar-check-package-in-pocket "$name" "$copy_pocket"
	fi
}

case "$action" in
	repo)
	validate_repo_dir
	validate_tag_name
	;;
	srpm)
	validate_srpm
	;;
	del)
	check_package_in_repo "$package" ||
		fatal "Invalid request to delete a nonexistent package \`$package' from \`$repo_name'"
	;;
	rebuild)
	girar-check-package-in-repo "$package" "$repo" ||
		fatal "Invalid request to rebuild a nonexistent package \`$package' in \`$repo'"
	validate_rebuild_dir
	;;
	kmodules)
	handle_kmodules
	exit 0
	;;
	copy)
	[ "$repo_name" != "$copy_repo_name" ] ||
		fatal "Invalid request to copy package from \`$copy_repo_name' to \`$repo_name'"
	[ "$repo_name" != 'sisyphus' ] ||
		fatal "Invalid request to copy package to \`$repo_name'"
	check_package_in_copy_repo "$package" ||
		fatal "Invalid request to copy nonexistent package \`$package' from \`$copy_repo_name'"
	;;
	kmod)
	gear_branch="$repo"
	module_branch="$kernel_name/$gear_branch"
	if [ -z "$pocket" ]; then
		gear_dir="/gears/k/$module_name.git"
		[ "$repo_name" = "sisyphus" ] &&
			template_branch="template/$module/sisyphus" ||
				template_branch="template/$module/alt-linux-$repo"
	else
		gear_dir="/pockets/$pocket/files/gears/k/$module_name.git"
		template_branch="template/$module/$pocket"
	fi
	validate_template_branch
	check_package_in_all_repos "$kernel_name" ||
		fatal "Invalid request to build for nonexistent kernel \`$kernel_name' from \`$repo_name'"
	;;
esac
cd "$GB_TASKS/$id"

check_task_modifiable

nums=$(gear_nums)

check_already_added_repo()
{
	local i
	for i in $nums; do
		[ -s gears/$i/dir ] || return 0
		local a_dir a_tag_name a_tag_id
		a_dir=$(cat gears/$i/dir)
		a_tag_name=$(cat gears/$i/tag_name)
		a_tag_id=$(cat gears/$i/tag_id)
		if [ "$a_tag_id" = "$tag_id" ]; then
			# same tag
			fatal "${dir##*/}: this tag $tag_name was already added (subtask #$i, from $a_dir $a_tag_name)"
		fi
		if [ "$a_tag_name" = "$tag_name" ] &&
		   [ "${a_dir##*/}" = "${dir##*/}" ]; then
			# different tags with the same name, from very similar git dirs.
			fatal "${dir##*/}: another tag $tag_name was already added (subtask #$i, from $a_dir)"
		fi
	done
}

check_already_added_package()
{
	local i
	for i in $nums; do
		[ -s gears/$i/package ] || continue
		local a_name reason
		a_name=$(cat gears/$i/package)
		[ "$a_name" = "$package" ] || continue
		if [ -f gears/$i/copy_repo ]; then
			reason="copy from $(cat gears/$i/copy_repo)"
		elif [ -f gears/$i/dir ]; then
			reason="rebuild from $(cat gears/$i/dir) $(cat gears/$i/tag_name)"
		else
			reason="deletion"
		fi
		fatal "package $package was already queued by $(cat gears/$i/userid) for $reason"
	done
}

check_already_added_srpm()
{
	local i
	for i in $nums; do
		[ -s gears/$i/srpm -a -s gears/$i/nevr ] || continue
		local a_nevr a_name
		a_nevr="$(cat gears/$i/nevr)"
		a_name="${a_nevr%	*}"
		[ "$a_name" = "$package" ] || continue
		fatal "package $(cat gears/$i/srpm) was already queued by $(cat gears/$i/userid) for build"
	done
}

check_already_added()
{
	case "$action" in
		repo)
		check_already_added_repo
		;;
		srpm|del|copy)
		check_already_added_srpm
		check_already_added_package
		;;
		rebuild)
		check_already_added_repo
		check_already_added_srpm
		check_already_added_package
		;;
	esac
}

check_add_first()
{
	[ -z "$subtask_id" ] ||
		fatal "subtask #$subtask_id not found"
	local task_owner
	task_owner=$(cat task/owner)
	[ "$GIRAR_USER" = "$task_owner" ] ||
		fatal "only $task_owner can add first subtask"
}

check_add_nth()
{
	if [ -n "$subtask_id" ]; then
		local i
		for i in $nums; do
			[ "$i" != "$subtask_id" ] || break
		done
		[ "$i" = "$subtask_id" ] ||
			fatal "subtask #$subtask_id not found"
	fi
	[ -w gears ] ||
		fatal 'gears: Permission denied'
}

find_new_subtask_id()
{
	local i
	if [ -z "$subtask_id" ]; then
		i=$(printf %s "$nums" |tail -n1)
		printf %o $((0$i+0100))
		return
	fi
	local prev=
	for i in $nums; do
		[ "$i" != "$subtask_id" ] || break
		prev="$i"
	done
	[ "$i" = "$subtask_id" ] ||
		fatal "subtask #$subtask_id not found"
	[ -n "$prev" ] || prev=0
	i=$(((0$prev+0$subtask_id)/2))
	i=$(printf %o "$i")
	[ "$i" -gt "$prev" -a "$i" -lt "$subtask_id" ] ||
		fatal "no room left before subtask #$subtask_id"
	printf %s $i
}

if [ -n "$nums" ]; then
	check_add_nth
	check_already_added
	i=$(find_new_subtask_id)
else
	check_add_first
	i=100
fi


logger -t "$PROG" "user=$GIRAR_USER task=$id subtask=$i"

atexit()
{
	local rc=$?
	trap - EXIT
	[ "$rc" -eq 0 ] || rm -rf gears/"$i"/ acl/"$i"/
	exit $rc
}

trap '' HUP INT QUIT PIPE TERM
# gears are not shared by default
mkdir -pm755 gears acl
mkdir -m2775 gears/"$i"
mkdir -m3775 acl/"$i"
trap atexit EXIT

case "$action" in
	del)
	printf '%s\n' "$package" >gears/$i/package
	printf '%s\n' "$GIRAR_USER" >gears/$i/userid
	girar-task-change-state "$id" NEW
	if [ -n "$pocket" ]; then
		echo >&2 "task #$id: added #$i: delete package $package from pocket $pocket based on $repo"
	else
		echo >&2 "task #$id: added #$i: delete package $package from $repo"
	fi
	exit 0
	;;
	copy)
	[ -z "$copy_pocket" ] ||
		printf '%s\n' "$copy_pocket" >gears/$i/copy_pocket
	printf '%s\n' "$copy_repo" >gears/$i/copy_repo
	printf '%s\n' "$package" >gears/$i/package
	printf '%s\n' "$GIRAR_USER" >gears/$i/userid
	girar-task-change-state "$id" NEW
	if [ -n "$copy_pocket" ]; then
		echo >&2 "task #$id: added #$i: copy package $package from pocket $copy_pocket based on $copy_repo"
	else
		echo >&2 "task #$id: added #$i: copy package $package from $copy_repo"
	fi
	exit 0
	;;
	srpm)
	mv -- "$HOME/incoming/tmp/$srpm" gears/$i/
	printf '%s\n' "$srpm" >gears/$i/srpm
	printf '%s\n' "$GIRAR_USER" >gears/$i/userid
	printf '%s\n' "$nevr" >gears/$i/nevr
	girar-task-change-state "$id" NEW
	echo >&2 "task #$id: added #$i: build srpm $srpm"
	exit 0
	;;
	genbases)
	printf '%s\n' "genbasedir" >gears/$i/genbases
	printf '%s\n' "gears/$i/genbases" >>task/regen
	printf '%s\n' "$GIRAR_USER" >gears/$i/userid
	echo >&2 "task #$id: added #$i: genbases"
	exit 0
	;;
	branch)
	printf '%s\n' "$branch_name" >gears/$i/branch_name
	printf '%s\n' "$branch_id" >gears/$i/branch_id
	;;
	kmod)
	printf '%s\n' "$flavour" >gears/$i/flavour
	printf '%s\n' "$module" >gears/$i/module
	printf '%s\n' "$kernel_name" >gears/$i/kernel_name
	printf '%s\n' "$module_name" >gears/$i/module_name
	printf '%s\n' "$template_branch" >gears/$i/template_branch
	printf '%s\n' "$module_branch" >gears/$i/module_branch
	printf '%s\n' "$branch_id" >gears/$i/template_id
	;;
esac

GIT_DIR=gears/$i/git
export GIT_DIR
mkdir "$GIT_DIR"
git init -q --template=/var/empty
if [ "$action" = rebuild ]; then
	echo "$dir/objects" > "$GIT_DIR"/objects/info/alternates
elif [ "$action" = branch ]; then
	printf >&2 %s "fetching branch \"$branch_name\" from $dir... "
	git fetch -q "$dir" "$branch_name:master"
	echo >&2 done

	girar-autorelease "$GIT_DIR" "gears/$i/tag_name" $repo_name
	tag_name=$(cat gears/$i/tag_name)
	validate_tag_name "$GIRAR_USER"
elif [ "$action" = kmod ]; then
	printf >&2 %s "fetching module template \"$template_branch\" from $dir... "
	git fetch -q "$dir" "$template_branch:$template_branch"
	echo >&2 done

	if [ -d "$gear_dir" ]; then
		printf >&2 %s "try fetching gear \"$module_branch\" from $gear_dir... "
		git fetch -q "$gear_dir" "$gear_branch:$module_branch" 2>/dev/null &&
			echo >&2 done ||
			echo >&2 "not found"
	fi

	girar-kmod-autorelease "$GIT_DIR" "gears/$i/tag_name" $template_branch $module_branch $flavour $repo_name
	tag_name=$(cat gears/$i/tag_name)
	validate_tag_name "$GIRAR_USER"
else
	printf >&2 %s "fetching tag \"$tag_name\" from $dir... "
	git fetch -q "$dir" tag "$tag_name"
	echo >&2 done
	git branch master "tags/$tag_name"
fi
git tag -l --points-at master |
	while read t; do
		[ "$t" = "$tag_name" ] ||
			git tag -d "$t"
	done
git pack-refs --all --prune
find "$GIT_DIR/refs" -mindepth 1 -type d -delete ||:
touch "$GIT_DIR/git-daemon-export-ok"

printf '%s\n' "$dir" >gears/$i/dir
printf '%s\n' "$tag_name" >gears/$i/tag_name
printf '%s\n' "$tag_id" >gears/$i/tag_id
printf '%s\n' "$tag_author" >gears/$i/tag_author
printf '%s\n' "$GIRAR_USER" >gears/$i/userid
if [ "$action" = rebuild ]; then
	printf '%s\n' "$package" >gears/$i/package
fi

if [ -O gears ]; then
	# git directories should be writable by builder
	find "$GIT_DIR" -type d -execdir chmod g+w -- '{}' '+'
else
	# directories added to alien task should be writable by task owner
	find "gears/$i" -type d -execdir chmod g+w -- '{}' '+'
fi

pkg_tar="gears/$i/pkg.tar"
printf >&2 %s "generating pkg.tar for ${dir##*/} tag \"$tag_name\"... "
if env TZ=UTC gear -t "$tag_name" -- "$pkg_tar" &&
   [ -s "$pkg_tar" ]; then
	echo >&2 done
else
	rm -f "$pkg_tar"
	fatal "failed to create pkg.tar for ${dir##*/} tag \"$tag_name\""
fi

girar-task-change-state "$id" NEW
if [ "$action" = branch ]; then
	echo >&2 "task #$id: added #$i: build branch \"$branch_name\" with tag \"$tag_name\" from $dir"
elif [ "$action" = kmod ]; then
	echo >&2 "task #$id: added #$i: build module \"$module_name\" with tag \"$tag_name\" from $dir"
else
	echo >&2 "task #$id: added #$i: build tag \"$tag_name\" from $dir"
fi
