#!/bin/sh ############################################################################### # # Package Frontend. # # By: Sulev-Madis Silber # # ############################################################################### # Configuration file CONFIG_FILE=/home/ketas/devel/pkgfe/pkgfe.conf # Paths PORTS_PATH=${PORTSDIR:-/usr/ports} PKGDB_PATH=${PKG_DBDIR:-/var/db/pkg} TEMP_DIR=${TMPDIR:-/tmp} # Miscellaneous PAGER_PROGRAM=${PAGER:-less} ############################################################################### # # Returns program name without path. # prog() { basename "$0" } # # Returns "s" if value is not "1". # plural() { if [ ! "$1" ] then ierror 'plural: invalid parameters' fi if [ "$1" -ne 1 ] then echo s fi } # # Removes "'" from a stream. # makesafe() { tr -d "'" } # # Removes file. # unlink_file() { if [ ! "$1" ] then ierror 'unlink_file: invalid parameters' fi if [ -f "$1" ] then rm -f "$1" fi } # # Prints error and exits. # error() { if [ ! "$1" ] then ierror 'error: invalid parameters' fi echo "`prog`: $1" >&2 cleanexit 1 } # # Prints internal error and exits. # ierror() { if [ ! "$1" ] then ierror 'ierror: invalid parameters' fi echo "`prog`: internal error: $1" >&2 cleanexit 1 } # # Prints error. # notice() { if [ ! "$1" ] then ierror 'notice: invalid parameters' fi echo "`prog`: $1" >&2 } # # Prints usage and exits. # usage() { if [ ! "$1" ] then ierror 'usage: invalid parameters' fi echo "usage: `prog` $1" >&2 cleanexit 1 } # # Does clean exit. # cleanexit() { del_tempfile unlink_file "$lock_file" exit ${1:-0} } # # Prints error when command fails. # exitcode() { if [ ! "$1" -o ! "$2" ] then ierror 'exitcode: invalid parameters' fi echo error "command '$1' failed with exit code $2" echo } # # Show command before executing it. # showcmd() { if [ ! "$1" ] then ierror 'showcmd: invalid parameters' fi echo echo "> $1" } # # Checks boolean configuration paramaters. # checkyesno() { if [ ! "$1" ] then ierror 'checkyesno: invalid parameters' fi eval local value=\$$1 case "$value" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) unset value return 0 ;; [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) unset value return 1 ;; *) error "variable '$1' is not set properly in '$CONFIG_FILE'" ;; esac } # # Checks if command is failed # (exit code is greater than zero). # cmd_failed() { if [ "$1" -gt 0 ] then return 0 else return 1 fi } # # Creates temporary file. # make_tempfile() { touch "$temp_file" if cmd_failed $? then error "cannot create temp file '$temp_file'" fi } # # Deletes temporary file. # del_tempfile() { unlink_file "$temp_file" } # # Shows menu or checklist using dialog(1). # show_dialog() { if [ $# -ne 3 -a $# -ne 4 ] then ierror 'show_dialog: invalid parameters' fi local name text options type stderr_file exitcode selected_options name=$1 text=$2 options=$3 type=checklist stderr_file="$temp_file" case $4 in ''|checklist) make_tempfile ;; menu) type=$4 stderr_file=/dev/null ;; *) ierror 'show_dialog: invalid type' ;; esac if [ ! "$name" ] then ierror 'show_dialog: name is empty' elif [ ! "$text" ] then ierror 'show_dialog: text is empty' elif [ ! "$options" ] then ierror 'show_dialog: options is empty' fi eval "dialog --title '`prog`: $name'"\ "--$type '\n$text\n'"\ "-1 -1 $dialog_list_rows"\ "$options"\ "2> '$stderr_file'" exitcode=$? eval selopt_${name}_exitcode=\$exitcode unset text options stderr_file exitcode if [ "$type" = checklist ] then selected_options=`sed 's|"||g' < "$temp_file"` del_tempfile eval selopt_${name}_values="\$selected_options" unset selected_options fi unset name type } # # Shows confirm dialog. # show_confirm() { if [ ! "$1" -o ! "$2" ] then ierror 'show_confirm: invalid parameters' fi local type package comment packages count dialog_options confirm_message cancel_message type=$1 packages=$2 for package in $packages do count=$(($count + 1)) comment=' ' if [ "$type" = upgrade ] then if checkyesno upgrade_show_new_version then comment=`grep "$package" "$status_file" | awk '{print "-> "$2}'` fi fi dialog_options="$dialog_options '$package' '$comment'" done unset package comment packages case $type in upgrade) confirm_message="Upgrade $count package`plural $count`?" cancel_message='Upgrade cancelled' ;; deinstall) confirm_message="Deinstall $count package`plural $count`?\nARE YOU SURE?" cancel_message='Deinstall cancelled' ;; install) confirm_message="Install $count package`plural $count`?" cancel_message='Install cancelled' ;; *) ierror 'show_confirm: invalid type' ;; esac unset type count show_dialog confirm "$confirm_message" "$dialog_options" menu unset dialog_options confirm_message if cmd_failed $selopt_confirm_exitcode then echo echo "$cancel_message" echo cleanexit fi unset cancel_message } # # Shows packages before executing # install, upgrade or deinstall. # show_packages() { if [ ! "$1" -o ! "$2" ] then ierror 'show_packages: invalid parameters' fi local package echo echo "$1:" echo shift for package in $* do echo "$package" done echo unset package } # # Shows package deinstall dialog. # package_deinstall() { local packages command exitcode packages=$1 if [ ! "$packages" ] then echo echo 'No packages selected for deinstall' echo cleanexit fi show_confirm deinstall "$packages" command="${root_command_prefix}pkg_deinstall $packages" if ! checkyesno enable_real_deinstall then cat <<- EOF Execute that manually: $command EOF cleanexit fi show_packages Deinstalling $packages unset packages showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode else unset command exitcode if checkyesno listup_after_deinstall then echo do_list fi fi cleanexit } # # Command: # shows help. for more info, look EOF. # do_help() { cat <<- EOF Help of `prog` is now moved to man page, see pkgfe(8) EOF cleanexit } # # Command: # shows list of outdated packages. # do_list() { local portupgrade_extra_arg command exitcode count make_tempfile pkg_info -E 'portupgrade>=2.4.6,2' > /dev/null exitcode=$? if ! cmd_failed $exitcode then portupgrade_extra_arg=F fi command="${root_command_prefix}portversion -${portupgrade_extra_arg}vl <" showcmd "$command" $command > "$temp_file" exitcode=$? if cmd_failed $exitcode then del_tempfile exitcode "$command" $exitcode fi unset command exitcode sed 's|<||; s|needs updating (port has ||; s|) *$||; s|) (| |; s| => .*$||' \ < "$temp_file" | \ makesafe > "$status_file" del_tempfile if ! checkyesno ignore_held then make_tempfile cat "$status_file" > "$temp_file" grep -v '\[held\]' < "$temp_file" > "$status_file" del_tempfile fi count=`wc -l "$status_file" | awk '{print $1}'` echo echo '-----------------------------------' echo "Found $count outdated package`plural $count`" echo '-----------------------------------' echo if [ "$count" -eq 0 -a "$1" = exit-if-none ] then cleanexit fi unset count } # # Command: # updates ports tree using csup(1). # do_csup() { local command exitcode check_supfile command="${root_command_prefix}csup $csup_args $csup_supfile" showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi command="${root_command_prefix}portsdb -Fu" echo showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi unset command exitcode echo do_list } # # Command: # updates ports tree using portsnap(8). # do_portsnap() { local command exitcode command="${root_command_prefix}portsnap fetch" if ! checkyesno old_portsnap then command="$command update" fi showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi if checkyesno old_portsnap then command="${root_command_prefix}portsnap update" echo showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi fi command="${root_command_prefix}portsdb -u" echo showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi unset command exitcode echo do_list } # # Command: # fixes package database. # do_fixdb() { local command exitcode command="${root_command_prefix}pkgdb -F" showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi unset command exitcode echo cleanexit } # # Command: # does search in ports tree. # do_find() { local search_string type search_string=$1 type=name case $search_string in name|key) search_string=$2 type=$1 ;; esac if [ ! "$search_string" ] then usage 'find [name|key] [regex]' fi showcmd "cd $PORTS_PATH && make search $type=$search_string" cd "$PORTS_PATH" || cleanexit 1 make_tempfile make search "$type=$search_string" > "$temp_file" if [ ! -s "$temp_file" ] then del_tempfile echo error "no matches for '$search_string'" fi unset search_string type clear $PAGER_PROGRAM "$temp_file" del_tempfile cleanexit } # # Command: # does "make config" in port's directory. # do_config() { local portpath portname=$1 if [ ! "$portname" ] then usage 'c [port]' fi portpath=`whereis -sB "$PORTS_PATH" -f "$portname" | cut -d ' ' -f 2` if [ ! -d "$portpath" ] then error "port '$portname' not found" fi showcmd "cd $portpath && make config" cd "$portpath" || cleanexit 1 ${root_command_prefix}make config unset portpath cleanexit } # # Command: # does search in ports tree # and shows install dialog. # do_find_install() { local search_string index_file name comments count dialog_options search_string=$1 index_file="$PORTS_PATH/INDEX-`uname -r | cut -d . -f 1`" if [ ! -f "$index_file" ] then if [ -f "$PORTS_PATH/INDEX" ] then index_file="$PORTS_PATH/INDEX" else error "no valid INDEX file found under '$PORTS_PATH'" fi fi if [ ! "$search_string" ] then usage 'findi [regex]' fi make_tempfile cut -d '|' -f 1,4 "$index_file" | makesafe | grep -iE -- "$search_string" | \ awk -F '|' '{print $1" "(length($2) > '$findi_description_length' ? substr($2, 0, '$findi_description_length')"..." : $2)}' \ > "$temp_file" unset index_file if [ ! -s "$temp_file" ] then del_tempfile error "no packages matching '$search_string' found" fi unset search_string count=`wc -l "$temp_file" | awk '{print $1}'` echo echo "$count result`plural $count` found. Generating dialog..." echo while read name comments do dialog_options="$dialog_options '$name' '$comments' OFF" done \ < "$temp_file" del_tempfile unset name comments show_dialog find "Found $count package`plural $count`.\nSelect one`plural $count` you want to install" "$dialog_options" unset count dialog_options if [ ! "$selopt_find_values" ] then echo echo 'No packages selected for install' echo cleanexit fi show_confirm install "$selopt_find_values" do_portinstall_portupgrade portinstall $selopt_find_values } # # Command: # searches for local packages. # do_ls() { local search_string=$1 if [ ! "$search_string" ] then usage 'ls [regex]' fi ls -1 "$PKGDB_PATH" | \ grep -v '^pkgdb\.' | \ grep -iE -- "$search_string" if cmd_failed $? then error "no packages matching '$search_string' found" fi unset search_string cleanexit } # # Command: # deinstalls not required packages. # do_notreq() { local old_wd name count dialog_options old_wd=`pwd` cd "$PKGDB_PATH" || cleanexit 1 for name in `ls -1 | grep -v '^pkgdb\.' | makesafe` do count=$(($count + 1)) if [ ! -f $name/+REQUIRED_BY ] then dialog_options="$dialog_options '$name' 'no +REQUIRED_BY' OFF" elif [ ! -s $name/+REQUIRED_BY ] then dialog_options="$dialog_options '$name' 'empty +REQUIRED_BY' OFF" fi done cd "$old_wd" || cleanexit 1 unset old_wd name show_dialog notrequired "Found $count not required package`plural $count`\nSelect one`plural $count` you want to deinstall" "$dialog_options" unset count dialog_options package_deinstall "$selopt_notrequired_values" } # # Command: # deinstalls packages. # do_deinst() { local old_wd name count dialog_options old_wd=`pwd` cd "$PKGDB_PATH" || cleanexit 1 for name in `ls -1 | grep -v '^pkgdb\.' | makesafe` do count=$(($count + 1)) if [ -f $name/+REQUIRED_BY ] then dialog_options="$dialog_options '$name' 'Required by others' OFF" else dialog_options="$dialog_options '$name' ' ' OFF" fi done cd "$old_wd" || cleanexit 1 unset old_wd name show_dialog deinstall "Found $count package`plural $count`.\nSelect one`plural $count` you want to deinstall" "$dialog_options" unset count dialog_options package_deinstall "$selopt_deinstall_values" } # # Command: # updates portaudit database. # do_padb() { local command exitcode command="${root_command_prefix}portaudit -Fd" showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode fi unset command exitcode echo } # # Command: # shows upgrade dialog. # do_upgrade() { local name comments count dialog_options if [ ! -s "$status_file" ] then do_list exit-if-none fi count=`wc -l "$status_file" | awk '{print $1}'` while read name comments do dialog_options="$dialog_options '$name' '< $comments' OFF" done \ < "$status_file" unset name comments show_dialog list "Found $count outdated package`plural $count`\nSelect one`plural $count` you want to upgrade" "$dialog_options" unset count dialog_options if [ ! "$selopt_list_values" ] then echo echo 'No packages selected for upgrade' echo cleanexit fi show_confirm upgrade "$selopt_list_values" do_portinstall_portupgrade portupgrade $selopt_list_values } # # Command: # runs portinstall(1) or portupgrade(1) with # verbose mode and additional arguments. # do_portinstall_portupgrade() { local program message command exitcode program=$1 shift case $program in portinstall) message=Installing ;; portupgrade) message=Upgrading ;; *) ierror 'do_portinstall_portupgrade: invalid program' ;; esac if [ ! "$*" ] then ierror 'do_portinstall_portupgrade: arguments cannot be empty' fi show_packages $message $* if checkyesno update_portaudit_db then do_padb fi command="$root_command_prefix$program $portupgrade_args $*" showcmd "$command" $command exitcode=$? if cmd_failed $exitcode then exitcode "$command" $exitcode elif [ $program = portupgrade ] then if checkyesno listup_after_upgrade then echo do_list fi fi unset program command exitcode cleanexit } # # Command: # checks for existence of required programs. # do_checkbinaries() { local binary exitcode exitcode=0 echo 'Checking for existence of binaries we need...' echo for binary in portupgrade portinstall portversion \ dialog csup portsdb pkgdb portaudit pkg_deinstall \ sed grep cat touch id uname cut portsnap awk wc whereis do which "$binary" > /dev/null if cmd_failed $? then notice "$binary: program not found" exitcode=1 else echo "$binary: OK" fi done unset binary cleanexit $exitcode } # # Command: # shows configuration. # do_showconfig() { local default if [ ! -f "$CONFIG_FILE" ] then notice "configuration file '$CONFIG_FILE' is not found" default=1 elif [ ! "$root_command_prefix" -a ! -r "$CONFIG_FILE" ] then notice "configuration file '$CONFIG_FILE' is not readable" default=1 fi echo if [ "$default" ] then echo 'using default configuration:' else echo "$CONFIG_FILE:" fi unset default cat <<- EOF --------------------------------------------------------------------- portupgrade_logdir="$portupgrade_logdir" portupgrade_args="$portupgrade_args" csup_supfile="$csup_supfile" csup_args="$csup_args" root_command_prefix="$root_command_prefix" status_file="$status_file" lock_file="$lock_file" temp_file="$temp_file" dialog_list_rows="$dialog_list_rows" findi_description_length="$findi_description_length" listup_after_upgrade="$listup_after_upgrade" ignore_held="$ignore_held" update_portaudit_db="$update_portaudit_db" enable_real_deinstall="$enable_real_deinstall" listup_after_deinstall="$listup_after_deinstall" old_portsnap="$old_portsnap" --------------------------------------------------------------------- EOF cleanexit } # # Checks boolean variables in configuration file. # check_boolean_variables() { local variable for variable in \ listup_after_upgrade \ ignore_held \ update_portaudit_db \ enable_real_deinstall \ listup_after_deinstall \ old_portsnap \ upgrade_show_new_version do checkyesno $variable done unset variable } # # Checks numeric variables in configuration file. # check_numeric_variables() { local variable value for variable in \ dialog_list_rows \ findi_description_length do eval value=\$$variable case "$value" in [0-9]|[0-9][0-9]) ;; *) error "variable '$variable' has invalid or not numeric value in '$CONFIG_FILE'" ;; esac done unset variable value } # # Checks (and creates) status file. # check_statusfile() { if [ ! -f "$status_file" ] then touch "$status_file" if cmd_failed $? then error "cannot create status file '$status_file'" fi fi } # # Checks csup supfile. # check_supfile() { if [ ! "$csup_supfile" ] then error "please define csup_supfile in '$CONFIG_FILE'" elif [ ! -f "$csup_supfile" ] then error "supfile '$csup_supfile' is not found" elif [ ! "$root_command_prefix" -a ! -r "$csup_supfile" ] then error "supfile '$csup_supfile' is not readable" fi } # # Checks portupgrade log directory. # check_portupgrade_logdir() { if [ ! "$portupgrade_logdir" ] then return 0 fi if [ ! -d "$portupgrade_logdir" ] then notice "portupgrade log directory '$portupgrade_logdir' is not found" elif [ ! "$root_command_prefix" -a ! -w "$portupgrade_logdir" ] then notice "portupgrade log directory '$portupgrade_logdir' is not writable" else return 0 fi cat <<- EOF If you don't want logging, comment out following varibles in $CONFIG_FILE: portupgrade_logdir portupgrade_args EOF cleanexit 1 } # # Checks (and creates) lock file. # check_lockfile() { if [ -f "$lock_file" ] then notice "lock file '$lock_file' exists" cat <<- EOF This probably means that you have another `prog` session running. If there are no active sessions, then it's probably a result of unfinished session. In this case, you must delete the file manually, before you can run `prog` again. EOF exit 1 fi touch "$lock_file" if cmd_failed $? then error "cannot create lock file '$lock_file'" fi } # # Checks for privileges. # check_privs() { if [ `id -u` -gt 0 ] then if [ ! "$root_command_prefix" ] then notice 'please switch to root' cat <<- EOF Alternatively, define root_command_prefix in $CONFIG_FILE EOF cleanexit 1 fi else root_command_prefix= fi } ############################################################################### if [ -f "$CONFIG_FILE" -a -r "$CONFIG_FILE" ] then . "$CONFIG_FILE" fi : ${portupgrade_logdir=""} : ${portupgrade_args="-v"} : ${csup_supfile=""} : ${csup_args="-L 2"} : ${root_command_prefix="sudo "} : ${status_file="$TEMP_DIR/`prog`.status"} : ${lock_file="$TEMP_DIR/`prog`.lock"} : ${temp_file="$TEMP_DIR/`prog`.tmp"} : ${dialog_list_rows="10"} : ${findi_description_length="26"} : ${listup_after_upgrade="YES"} : ${ignore_held="YES"} : ${update_portaudit_db="YES"} : ${enable_real_deinstall="NO"} : ${listup_after_deinstall="YES"} : ${old_portsnap="NO"} : ${upgrade_show_new_version="NO"} check_lockfile trap 'cleanexit 1' 1 2 15 30 31 if [ `uname -s` != FreeBSD ] then error 'requires FreeBSD' fi check_boolean_variables check_numeric_variables check_privs check_portupgrade_logdir check_statusfile ############################################################################### case $1 in '') do_upgrade ;; listup|list) do_list cleanexit ;; cvsup|cvs|csup) do_csup cleanexit ;; portsnap|snap) do_portsnap cleanexit ;; all) do_csup do_upgrade ;; snap+run) do_portsnap do_upgrade ;; listup+run|list+run) do_list do_upgrade ;; fixdb|fix) do_fixdb ;; find|portfind) shift do_find "$1" "$2" ;; config|conf|c) shift do_config "$1" ;; findi|findv) shift do_find_install "$1" ;; ls|lspkg) shift do_ls "$1" ;; chkreq|notreq|noreq) do_notreq ;; deinst|deins) do_deinst ;; padb) do_padb cleanexit ;; install|i) shift if [ ! "$*" ] then usage 'i [port]' fi do_portinstall_portupgrade portinstall $* ;; upgrade|u) shift if [ ! "$*" ] then usage 'u [port]' fi do_portinstall_portupgrade portupgrade $* ;; chkbin) do_checkbinaries ;; showconf) do_showconfig ;; help) do_help ;; *) error "invalid parameter '$1'" ;; esac cleanexit