#!/usr/bin/zsh # # Copyright (c) 2006 Jan Srzednicki # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # $Id: zsh_rcc,v 1.24 2008/07/08 09:51:06 winfried Exp $ ################################################################### # RCC - Universal init (aka rc) script interface. Currently # supported: FreeBSD and Linux with /etc/init.d # # USAGE: # # Add this to your ~/.zshrc file after compinit has been loaded. # . path/to/zsh_rcc # # If you want to override default by defining the following # variables _before_ sourcing this file: # # name: rcc_dirs type: array # descr: A list of directories where init/rc scripts are located # # name: rcc_skip_suffixes type: array # descr: A list of suffixes to ignore in the list. Set this to # ("[RCC_NONE]") to disable. # # name: rcc_skip_filenames type: array # descr: A list of filenames to ignore. Set this to ("[RCC_NONE]") to # disable. # # name: rcc_standard_cmds type: string, space separated list # descr: A list of commands which will be avalaible in completion # in case of a script I cannot parse. Default sets that to # 'start', 'stop' and 'restart'. # # WARNING: the builtin rehash is overriden by a function, to allow # rehashing of RCC data as well. #################################################################### setopt extended_glob # Global hash table with scripts data: typeset -Ag _rcc_initfiles_hash typeset -Ag _rcc_initfiles_cmds_hash # It's global to make things faster, it sits in the memory statically. # I assume the explanations are universal :P typeset -Ag _rcc_initfiles_cmds_descr_hash _rcc_initfiles_cmds_descr_hash=( 'start' 'Start the service' \ 'stop' 'Stop the service' \ 'reload' 'Reload the service' \ 'restart' 'Restart the service' \ 'rcvar' 'Check what rc.conf variables are responsible for the service' \ 'poll' 'Wait till the service stops' \ 'status' 'Check if the service is running' \ 'pause' 'Halt the service, but don'\''t affect anything depending on it' \ 'zap' 'Force the service status information to be "off"' \ 'ineed' 'List services on which this service depends' \ 'iuse' 'List services used (but not required) by this service' \ 'needsme' 'List services depending on this service' \ 'usesme' 'List services using (but not requiring) this service' \ 'broken' 'List missing dependencies for this service' \ 'help' 'Print help' \ 'usage' 'Print usage information' \ 'keygen' 'Generate keys' \ ) _rcc_initfiles() { # for loop variables local dir key suffix skip file # Real script path. local initfile # "Translated" initfile name, to act a key to the hash table local initfile_transl # Temporary hasz to detect duplicates local initfiles_temphash # Directories in which initscripts are located. Arch-dependent. # Allow manual override. # local rcc_dirs local rcc_dirs_tmp # Hash table with different RC script styles. # (it's possible to have several ones in one system) local rcc_scripts_style typeset -A rcc_scripts_style # Empty set in the begining: rcc_dirs_tmp=() # Try to detect what kind of scripts are there: if [[ "$OSTYPE" == freebsd* ]]; then rcc_scripts_style['FreeBSD']='YES' if [[ "$OSTYPE" > "freebsd5.0" && -d "/etc/rc.d" ]]; then # We have rcNG scripts in base system, treat them well. rcc_scripts_style['FreeBSD_rcNG']='YES' rcc_dirs_tmp+=("/etc/rc.d") fi if [[ -d "/usr/local/etc/rc.d" ]]; then rcc_dirs_tmp+=("/usr/local/etc/rc.d") fi elif [[ "$OSTYPE" == linux* ]]; then if [[ -d "/etc/init.d" ]]; then # SysV init and alikes rcc_scripts_style['SysV_Init']='YES' rcc_dirs_tmp+=("/etc/init.d") if [[ -f "/etc/gentoo-release" ]]; then rcc_scripts_style['Gentoo_runscript']='YES' fi if [[ -f "/etc/debian_version" ]]; then rcc_scripts_style['Debian_init.d']='YES' fi elif [[ -d "/etc/rc.d" && -f "/etc/slackware-version" ]]; then rcc_scripts_style['Slackware_rc.d']='YES' rcc_dirs_tmp+=("/etc/rc.d") fi fi [[ -z "$rcc_dirs" ]] && rcc_dirs=( $rcc_dirs_tmp ) # Unimplemented [[ -z "$rcc_dirs" ]] && return 0 # Files with these suffixes will be excluded: [[ -z "$rcc_skip_suffixes" ]] && \ rcc_skip_suffixes=( ',v' '~' \ '.example' '_example' \ '.sample' '_sample' \ '.dist' '_dist' \ '.old' '_old' \ '.orig' '_orig' \ '.disabled' '_disabled' '.backup' '_backup' ) # Skip those dummy services on FreeBSD 5.0+ if [[ -z "$rcc_skip_filenames" ]]; then if [[ "$rcc_scripts_style['FreeBSD_rcNG']" == 'YES' ]]; then rcc_skip_filenames=('DAEMON' 'LOGIN' 'NETWORKING' 'SERVERS') elif [[ "$rcc_scripts_style['Debian_init.d']" == 'YES' ]]; then rcc_skip_filenames=('.dpkg-old' '.dpkg-new') else rcc_skip_filenames=('[RCC_NONE]') fi fi # Let's find all those scripts. typeset -A initfiles_temphash for dir in $rcc_dirs[@]; do for initfile in ${dir}/*; do if [[ -x "$initfile" ]]; then skip="no" [[ "$rcc_skip_suffixes[0]" != '[RCC_NONE]' ]] && \ for suffix in $rcc_skip_suffixes; do [[ $initfile == *$suffix ]] && skip="yes" done [[ "$rcc_skip_filenames[0]" != '[RCC_NONE]' ]] && \ for file in $rcc_skip_filenames; do [[ ${initfile:t} == $file ]] && skip="yes" done if [[ "$skip" == "no" ]]; then initfile_transl=${initfile:t} if [[ -n "$initfiles_temphash[$initfile_transl]" ]]; then echo "RCC warning: duplicate initfile '${initfile_transl}', locations:" >&2 echo "'${initfile}' and '${initfiles_temphash[$initfile_transl]}'" >&2 echo "Skipping." >&2 else initfiles_temphash+=( ${initfile_transl} ${initfile} ) fi fi fi done done # Start with empty hash. _rcc_initfiles_hash=() _rcc_initfiles_cmds_hash=() [[ -z "$rcc_standard_cmds" ]] && rcc_standard_cmds="start stop restart" local cmds what magic magic_more for key in $initfiles_temphash[(I)*]; do # Sanitize names: remove traling '.sh' and leading '###.' (digits) initfile_transl=${${key%.sh}#[[:digit:]][[:digit:]][[:digit:]].} # Sanitize names: remove spaces initfile_transl=${initfile_transl// /} # Sanitize slackware: remove the leading 'rc.'. if [[ "$rcc_scripts_style['Slackware_rc.d']" == 'YES' ]]; then initfile_transl=${initfile_transl#rc.} fi if [[ "x$initfile_transl" != "x$key" \ && -n "$initfiles_temphash[$initfile_transl]" ]]; then echo "RCC info: could not sanitize '${key}' to '${initfile_transl}', as it already exists." >&2 initfile_transl=${key} fi # Just that :P cmds="" # Now let's try to parse those scripts to get the commands: if [[ "$rcc_scripts_style['FreeBSD']" == 'YES' ]]; then [[ -r ${initfiles_temphash[$key]} ]] && \ read -u0 -k11 magic < ${initfiles_temphash[$key]} if [[ "$magic" == '#!'[[:blank:]]#'/bin/sh'* ]]; then # Let's find out if it's a rcNG script; 200 bytes is a # safe default. [[ -r ${initfiles_temphash[$key]} ]] && \ read -u0 -k800 magic_more < ${initfiles_temphash[$key]} if [[ "$magic_more" == *[[:cntrl:]]'# PROVIDE: '* ]]; then # rcNG! cmds=${${${(M)${(f)"$(<${initfiles_temphash[$key]})"}:#[[:blank:]]#extra_commands=\"*\"}%\"}#[[:blank:]]#extra_commands=\"} # vim syntax fix -> " cmds="${cmds} start stop restart status rcvar poll" else # An old-style FreeBSD rc script. # Quite nuts, isn't it? what='(st(art|op|atus)|(force-|)re(start|load)|debug_(up|down)|dump(|_stats)|add|delete|clean|list|help|usage)' cmds=${(j: :)${(s:|:)${${(M)${(f)"$(<${initfiles_temphash[$key]})"}:#[[:blank:]]#(\'|)${~what}(\|${~what})#(\'|)\)}%%(\'|)\)*}##[[:blank:]]#(\'|)}} # vim syntax fix -> " fi fi fi # Try Gentoo: if [[ -z "$cmds" && "$rcc_scripts_style['Gentoo_runscript']" == 'YES' ]]; then [[ -r ${initfiles_temphash[$key]} ]] && \ read -u0 -k20 magic < ${initfiles_temphash[$key]} if [[ "$magic" == '#!'[[:blank:]]#'/sbin/runscript'* ]]; then cmds=${${${(M)${(f)"$(<${initfiles_temphash[$key]})"}:#[[:blank:]]#opts=\"*\"}%\"}#[[:blank:]]#opts=\"} # vim syntax fix -> " cmds="${cmds} start stop restart pause zap status ineed iuse needsme usesme broken" fi fi # On gentoo, if it's not a runscript script, try plain init.d. # On other /etc/init.d systems, try just that. if [[ -z "$cmds" && "$rcc_scripts_style['SysV_Init']" == 'YES' ]]; then what='(st(art|op|atus)|(force-|)re(start|load)|debug_(up|down)|dump(|_stats)|add|delete|clean|list|help|usage)' read -u0 -k14 magic < ${initfiles_temphash[$key]} && \ [[ "$magic" == '#!'[[:blank:]]#'/bin/'(ba|a|)'sh'* ]] && \ cmds=${(j: :)${(s:|:)${${(M)${(f)"$(<${initfiles_temphash[$key]})"}:#[[:blank:]]#(\'|)${~what}(\|${~what})#(\'|)\)}%%(\'|)\)*}##[[:blank:]]#(\'|)}} # vim syntax fix -> " fi # And finally, set the apropriate hashes. if [[ -z "$cmds" ]]; then _rcc_initfiles_cmds_hash+=( ${initfile_transl} "$rcc_standard_cmds" ) else _rcc_initfiles_cmds_hash+=( ${initfile_transl} "$cmds" ) fi _rcc_initfiles_hash+=( ${initfile_transl} ${initfiles_temphash[$key]} ) done } # Function for compdef _rcc() { local cmd values # Initialization. test ${#_rcc_initfiles_hash} -eq 0 && _rcc_initfiles if (( CURRENT == 2 )); then _values 'startup scripts' $_rcc_initfiles_hash[(I)*] elif (( CURRENT == 3 )); then # Script-specific settings can be put here # (just append them to the values array). values=() for cmd in ${=_rcc_initfiles_cmds_hash[$words[2]]}; do if [[ -n "${_rcc_initfiles_cmds_descr_hash[$cmd]}" ]]; then values+=( "${cmd}[${_rcc_initfiles_cmds_descr_hash[$cmd]}]" ) else values+=( "${cmd}[No description]" ) fi done # And finally if [[ ${#values} -eq 0 ]]; then # CaƂkowita podstawa _values 'commands' 'start' 'stop' else _values 'commands' $values[*] fi fi } # Override builtin rehash. rehash() { builtin rehash _rcc_initfiles } # The real function. rcc() { # Initialization. test ${#_rcc_initfiles_hash} -eq 0 && _rcc_initfiles if [[ -z "$1" || -z "$2" ]]; then echo "Usage: $0 " >&2 return 1 fi if [[ -z "$_rcc_initfiles_hash[$1]" ]]; then echo "Script $1 not found." >&2 return 1 fi $_rcc_initfiles_hash[$1] $argv[2,-1] return $? } compdef _rcc rcc