#!/bin/bash
set -eu
#
# pac-cyg --- Install tool for cygwin similar to archlinux pacman
#
# Copyright (C) 2014 10sr
#
# This program 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 3 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, see .
# (http://www.fsf.org/licensing/licenses/gpl.html)
use_cowsay=0
_mirrorlist_path=/etc/pac-cyg.mirrorlist
_version=0.1.4
_cachedir=/var/cache/pac-cyg
_setupdir=/etc/setup
_arch=
_tmpdir=
# newline separated mirrorlist
_mirrorlist_default="#
main http://mirrors.kernel.org/sourceware/cygwin"
_installed_db=$_setupdir/installed.db
enable_cowsay=0
if command -v cowsay >/dev/null \
&& test -n "$use_cowsay" && test "$use_cowsay" -gt 0
then
enable_cowsay=1
fi
##########################33
# Utilities
if command -v tput >/dev/null
then
__tput_bold=`tput bold`
__tput_default=`tput sgr0`
__tput_yellow=`tput setaf 3`
else
echo ">>> Executable tput is not available. Install ncurses to enable colorized output"
__tput_bold=
__tput_default=
__tput_yellow=
fi
_msg(){
if (( $enable_cowsay ))
then
cowsay "$*"
else
echo ">> ${__tput_bold}$*${__tput_default}"
fi
}
_warn(){
if (( $enable_cowsay )) >/dev/null
then
cowsay -d "$*" 1>&2
else
echo ">>> ${__tput_yellow}${__tput_bold}$*${__tput_default}" 1>&2
fi
}
_die(){
_warn "$@"
exit 1
}
_ask(){
# _ask msg default
# _ask use input with prompt MSG
# DEFAULT must be y or n
printf "$1 "
read __reply
test -n "$__reply" || __reply="$2"
case $__reply in
y|Y|yes|YES) return 0;;
n|N|no|NO) return 1;;
*) return 2;;
esac
}
_detect_arch_or_die(){
case "$HOSTTYPE" in
x86_64) echo x86_64;;
i686) echo x86;;
*) _die Unsupported architecture: $HOSTTYPE;;
esac
}
_download(){
# _download
if type wget >/dev/null 2>&1
then
wget "$1" -O "$2" # -N
elif type curl >/dev/null 2>&1
then
curl --url "$1" --output "$2"
else
_warn No program for download found.
_die Install wget or curl from setup.exe first.
fi
}
_get_cache_dir(){
# _get_cache_dir
# get cache directory for mirror_url
__mirror_dir="$_cachedir/`echo $1 | sed -e 's|/|%2f|g
s|:|%3a|g'`/$_arch"
mkdir -p "$__mirror_dir"
echo "$__mirror_dir"
}
_check_prepare_mirrorlist(){
# check if $_mirrorlist_path exits, and create newly if not yet with default
# value $_mirrorlist_default
# TODO: check if mirrorlist is in valid format
if test -e $_mirrorlist_path
then
return
fi
_msg $_mirrorlist_path not exists yet. creating it
echo "$_mirrorlist_default" >$_mirrorlist_path
}
_cat_mirrorlist(){
# cat mirrorlist with invalid line stripped
# grep -v '^#' $_mirrorlist_path | grep '^\w\+\W\+\w\+\W\+$'
grep -v '^#' $_mirrorlist_path
}
_is_installed(){
# _is_installed pkgname
# fail if pkgname is not installed yet
cut -d " " -f 1 $_installed_db | grep --line-regexp "$1" >/dev/null 2>&1
}
# FIXME: make the variable naming consistent: proceeding __ for local variables
##############################
# sync operations: install packages, search packages including uninstalled
_help_sync(){
cat <<__EOC__
usage: pac-cyg -S [ ...] [ ...]
options:
-s Search repositories
-i Show infos about packages
-y Update repository databases
-h Print this help
No option to install TARGET.
__EOC__
}
do_sync(){
__search=
__update=
__upgrade=
__info=
if test "$1" = "-"
then
shift 1
else
unset OPTIND
while getopts syuih opt2
do
case "$opt2" in
s) __search=1;;
y) __update=1;;
u) __upgrade=1;;
i) __info=1;;
h) _help_sync; return 0;;
*) _help_sync; return 1;;
esac
done
shift `expr $OPTIND - 1`
fi
if test -z "$__search" -a -z "$__update" -a -z "$__upgrade" -a -z "$__info"
then
for __pkg in "$@"
do
if _is_installed "$__pkg"
then
_msg Package $__pkg is already installed, skipping
else
_install_package "$__pkg"
fi
done
return 0
fi
if test -n "$__update"
then
_cat_mirrorlist | while read __repname __url
do
_update_pkglist $__url
done
fi
if test -n "$__upgrade"
then
# TODO: implement update
_warn Updating feature is not supported yet. Use setup.exe .
fi
if test -n "$__search"
then
_cat_mirrorlist | while read __repname __url
do
_msg "repository [$__repname]"
if test $# -eq 0
then
awk '$0 = $1' RS='\n\n@ ' FS='\n' \
`_get_cache_dir $__url`/setup.ini
else
for __pkg in "$@"
do
awk '$1 ~ query && $0 = $1' RS='\n\n@ ' FS='\n' query="$__pkg" \
`_get_cache_dir $__url`/setup.ini
done
fi
done
fi
if test -n "$__info"
then
_cat_mirrorlist | while read __repname __url
do
_msg "repository [$__repname]"
for __pkg in "$@"
do
awk '$1 == query {print $0}' RS='\n\n@ ' FS='\n' query="$__pkg" \
`_get_cache_dir $__url`/setup.ini
echo
done
done
fi
}
_update_pkglist()
{
# _update_pkglist
# fetch latest setup.ini from mirror
# TODO: use md5.sum to check if updated
__dir=`_get_cache_dir $1`
if _download $1/$_arch/setup.bz2 $__dir/_setup.bz2 && \
test -e $__dir/_setup.bz2
then
bunzip2 $__dir/_setup.bz2
mv $__dir/_setup $__dir/setup.ini
_msg Package list updated
elif _download $1/$_arch/setup.ini $__dir/_setup.ini && \
test -e $__dir/_setup.ini
then
mv $__dir/_setup.ini $__dir/setup.ini
_msg Package list updated
else
_die Error updating package list
fi
}
_install_package(){
# _install_package
# query one package for install
# check all available mirrors and install required packages
# basically install all dependant packages from same mirrors
__pkg=$1
_cat_mirrorlist | while read __repname __url
do
if _make_desc_or_fail $__pkg $__url
then
_msg $__pkg is found in $__repname
_install_recursively_with_mirror $__pkg $__url
_run_postinstall_scripts
break
else
_warn $__pkg is not found in $__repname
fi
done
if ! _is_installed $__pkg
then
_warn $__pkg not installed.
return 1
fi
}
_run_postinstall_scripts(){
# run all postinstall scripts
if ls /etc/postinstall/*.sh >/dev/null 2>&1
then
_msg Running postinstall scripts
for __script in /etc/postinstall/*.sh
do
_msg Run poistinstall script: $__script
sh $__script
mv $__script $__script.done
done
fi
}
_make_desc_or_fail(){
# _make_desc_or_fail
# If PKGNAME is found in MIRROR, make desc file for that.
# Otherwise, return 1
__pkg=$1
__mirror=$2
__dir="`_get_cache_dir $__mirror`"
__pkgdir=$__dir/release/$__pkg
# look for package and save desc file
mkdir -p $__pkgdir
awk '
$1 == package {
desc = $0
px++
}
END {
if (px == 1 && desc) print desc
else print "Package not found"
}
' RS='\n\n@ ' FS='\n' package="$__pkg" `_get_cache_dir $__mirror`/setup.ini \
> "$__pkgdir/desc"
if test "`cat $__pkgdir/desc`" = 'Package not found'
then
rm -r $__pkgdir
return 1
fi
}
_install_recursively_with_mirror(){
# _install_recursively_with_mirror
# desc file is already prepared
__pkg=$1
__mirror=$2
__dir="`_get_cache_dir $__mirror`"
__pkgdir=$__dir/release/$__pkg
_msg Start installing $__pkg
# download and unpack the bz2 or xz file
# pick the latest version, which comes first
__arch_relative_path=$(awk '/^install: / {print $2; exit}' "$__pkgdir/desc")
if test -z "$__arch_relative_path"
then
_msg Cannot find archive file for $__pkg in $__mirror
return 0
fi
__arch_url="$__mirror/$__arch_relative_path"
# __arch_output="$__dir/../$__arch_relative_path"
__arch_basename=`basename $__arch_relative_path`
__arch_output="$__pkgdir/$__arch_basename"
__sha512sum=$(awk '/^install: / {print $4; exit}' $__pkgdir/desc)
if test -e "$__arch_output" && \
test $(sha512sum $__arch_output | awk NF=1) = $__sha512sum
then
_msg $__arch_basename already downloaded. skip
elif _download $__arch_url $__arch_output && \
test $(sha512sum $__arch_output | awk NF=1) = $__sha512sum
then
# file donwloaded without any error
true
else
_msg downloading $__arch_basename failed
return 1
fi
_msg "Unpacking $__arch_output ..."
tar xvf $__arch_output -C / | gzip > "$_setupdir/$__pkg".lst.gz
# update installed.db
awk '
ins != 1 && pkg < $1 {
printf "%s %s 0\n", pkg, bz
ins=1
}
1
END {
if (ins != 1) printf "%s %s 0\n", pkg, bz
}
' pkg="$__pkg" bz=$__arch_basename $_installed_db >$_tmpdir/installed.db.$$
mv $_installed_db{,.bak}
mv $_tmpdir/installed.db.$$ $_installed_db
_msg Package $__pkg installed
# recursively install required packages
# TODO: use transaction: install packages only when all required packages
# are successfully extracted
__requires=$(awk '
$0 ~ rq {
sub(rq, "")
print
}
' rq='^requires: ' "$__pkgdir/desc")
__warn=0
if (( ${#__requires} ))
then
_msg Package $__pkg requires the following packages, installing:
echo " " $__requires
for __require in $__requires
do
if _is_installed $__require
then
_msg Package $__require is already installed, skipping
continue
fi
if _make_desc_or_fail $__require $__mirror
then
_install_recursively_with_mirror $__require $__mirror
(( $? && __warn++ )) || true
else
_warn $__require not found in $__mirror
_warn check another mirrors
_install_package $__require
(( $? && __warn++ )) || true
fi
done
fi
if (( __warn ))
then
_warn 'Warning: some required packages did not install, continuing'
fi
}
##############################
# query installed packages
_help_query(){
cat <<__EOC__
usage: pac-cyg -Q [ ...] [ ...]
options:
-s Search installed packages
-i Show infos about installed packages (very simple, use -Si for
details)
-o Find package who owns given files or commands
-l List files owned by packages
-h Print this help
No option to show all installed packages.
__EOC__
}
do_query(){
__search=
__info=
__own=
__list=
if test "$1" = "-"
then
shift 1
else
unset OPTIND
while getopts siolh opt2
do
case "$opt2" in
s) __search=1;;
i) __info=1;;
o) __own=1;;
l) __list=1;;
h) _help_query; return 0;;
*) _help_query; return 1;;
esac
done
shift `expr $OPTIND - 1`
fi
if test -z "$__search" -a -z "$__info" -a -z "$__own" -a -z "$__list"
then
# list all instaled packages
awk 'NR>1 && $0=$1' $_installed_db
return 0
fi
if test -n "$__search"
then
for __pkg in "$@"
do
awk 'NR>1 && $1~query && $0=$2' query="$__pkg" $_installed_db | \
sed -e 's/\.tar\..*//'
done
fi
if test -n "$__info"
then
# NOTE: only shows package name and version
for __pkg in "$@"
do
awk 'NR>1 && $1 == query && $0=$2' query="$__pkg" $_installed_db | \
sed -e 's/\.tar\..*//'
done
fi
if test -n "$__own"
then
for __e in "$@"
do
__key=$(command -v "$__e" | sed s./..)
if (( ! ${#__key} ))
then
__key=$__e
fi
for __manifest in "$_setupdir"/*.lst.gz
do
__found=$(gzip -cd $__manifest | grep -c "$__key" || true)
if (( __found ))
then
__package=$(sed '
s,/etc/setup/,,
s,.lst.gz,,
' <<< $__manifest)
_msg Found $__key in the package $__package
break
fi
done
done
fi
if test -n "$__list"
then
for __pkg in "$@"
do
zcat "$_setupdir"/"$__pkg".lst.gz 2>/dev/null \
|| _warn Package not found: "$__pkg"
done
fi
}
###############################
# remove packages
_help_remove(){
cat <<__EOC__
usage: pac-cyg -R [ ...] [ ...]
options:
-h Print this help
__EOC__
}
do_remove(){
__recursive=
if test "$1" = "-"
then
shift 1
else
unset OPTIND
while getopts sh opt2
do
case "$opt2" in
s) __recursive=1;;
h) _help_remove; return 0;;
*) _help_remove; return 1;;
esac
done
shift `expr $OPTIND - 1`
fi
if test "$#" -lt 1
then
_warn No packages to remove
return 0
fi
if test -n "$__recursive"
then
_warn Recursive remove is not currently supported.
_warn "-s" option is ignored.
fi
_msg Removing these packages:
echo
echo " " "$@"
echo
if _ask "Really remove these packages? [y/N]" n
then
for __pkg in "$@"
do
_remove_package "$__pkg"
done
else
_warn Abort.
fi
}
_remove_package(){
# _remove_package
# remove one package
__pkg=$1
if ! _is_installed "$__pkg"
then
_warn Package $__pkg is not installed, skipping
return 0
fi
for __req in cygwin coreutils gawk bzip2 tar bash
do
if [[ $__pkg = $__req ]]
then
_warn apt-cyg cannot remove package $__pkg, skipping
return 0
fi
done
if [ ! -e "$_setupdir/$__pkg.lst.gz" ]
then
_warn Package manifest missing, cannot remove $__pkg. skipping
return 0
fi
_msg Removing $__pkg
# run preremove scripts
if [ -e "/etc/preremove/$__pkg.sh" ]
then
"/etc/preremove/$__pkg.sh"
rm "/etc/preremove/$__pkg.sh"
fi
gzip -cd "$_setupdir/$__pkg.lst.gz" |
awk '/[^\/]$/ {print "rm -f \"/" $0 "\""}' | sh
rm "$_setupdir/$__pkg.lst.gz"
rm -f /etc/postinstall/$__pkg.sh.done
awk '$1 != pkg' pkg="$__pkg" $_installed_db > $_tmpdir/installed.db.$$
mv ${_installed_db}{,-save}
mv $_tmpdir/installed.db.$$ $_installed_db
_msg Package $__pkg removed
}
############################
# help
do_help()
{
cat <<__EOC__
usage: pac-cyg [ ...] [ ...]
Operations:
-S Sync packages
-Q Query installed packages
-R Remove packages
-V Show version
use 'pac-cyg -h' with an operation for available options
__EOC__
}
########################
# version
do_version()
{
cat <<__EOC__
pac-cyg v$_version
Copyright (C) 2014 10sr
This program may be freely redistributed under
the terms of the GNU General Public License.
__EOC__
}
######################################3
# main
_delete_tmpdir(){
# _msg "Cleaning tmpdir"
rm -rf "$_tmpdir"
}
main(){
_check_prepare_mirrorlist
__ope=
__opts=
__help=
unset OPTIND
while getopts SQRVsyuiolh opt
do
case "$opt" in
S) __ope=sync;;
Q) __ope=query;;
R) __ope=remove;;
V) __ope=version;;
s|y|u|i|o|l) __opts=$__opts$opt;;
h) __help=1; __opts=$__opts$opt;;
*) do_help; return 1;;
esac
done
shift `expr $OPTIND - 1`
# if no operations given and -h is given
if test -z "$__ope" -a -n "$__help"
then
do_help
return 0
fi
case "$__ope" in
sync) do_sync -"$__opts" "$@";;
query) do_query -"$__opts" "$@";;
remove) do_remove -"$__opts" "$@";;
version) do_version -"$__opts" "$@";;
*) do_help -"$__opts" "$@"; return 1;;
esac
}
_arch=`_detect_arch_or_die`
_tmpdir=`mktemp -d`
trap '_delete_tmpdir; exit 1' 1 2 3 15
trap '_delete_tmpdir' EXIT
main "$@"