#!/bin/sh #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # Copyright (c) Calvin Smith # # All rights reserved. # # 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. # 3. Neither the name of the author nor the names of his contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # History: # # 2008-05-15: added filename to grep output if more than 1 file is searched. # 2008-05-08: initial version, with edit, append, and grep functionality. # PROG="$(basename $0)" PORT_CONF_DIR="/etc/portage" usage() { cat <> $PORT_CONF_DIR/package.keywords in order to easily add a line to a portage configuration file, but non-root users can't do this, and you can't give sudo privileges for echo to a user without allowing them to overwrite anything (effectively making them root). Thus this script! The script should be saved somewhere that is on the PATH of all users, like /usr/local/bin, and it should have 755 permissions, so that no-one but the root user can edit it but all users can execute it. Using the --grep option works for all users and requires no further configuration. Using the --append option requires that you give the invoking user sudo privileges for this script. Using the --edit option requires that you additionally give the invoking user sudoedit privileges for each of the files in $PORT_CONF_DIR. For example, use the following (removing leading whitespace on the first line) to allow only appending to files by any user in the wheel group (without entering a password): %wheel ALL=NOPASSWD: /usr/local/bin/$PROG And use the following to allow any user in the wheel group to both append to or edit portage configuration files: %wheel ALL=NOPASSWD: /usr/local/bin/$PROG, \\ sudoedit $PORT_CONF_DIR/package.keywords, \\ sudoedit $PORT_CONF_DIR/package.use, \\ sudoedit $PORT_CONF_DIR/package.unmask, \\ sudoedit $PORT_CONF_DIR/package.mask ALIASES You may find it helpful to define the following aliases for the user that will use this script: alias pa="sudo $PROG --append" alias pe="sudo $PROG --edit" alias pg="sudo $PROG --grep" alias ph="sudo $PROG --help" Unmasking and adding a package to package.keywords is then as simple as: pa x11-libs/cairo package.unmask package.keywords SHORTCUTS This script also understands some shortcuts for filenames. On each of the following lines, the term on the left side can be used instead of the term on the right side: keywords -> package.keywords use -> package.use unmask -> package.unmask mask -> package.mask EOF } # Print an error message to stderr. err() { echo "$*" 1>&2 } # Expand one or more filenames into the full path to that # file in $PORT_CONF_DIR, failing if any path contains a slash, # returning 0 on success and 1 on failure. expandportageconfpaths() { if [ $# -eq 0 ]; then echo "" return 0 fi conf_dir="$PORT_CONF_DIR" fullpaths="" for filename in $@ do if expr match "$filename" ".*\/.*" > /dev/null ; then err "Invalid filename (contains '/'): $filename" err "File arg should be something like 'package.keywords'" return 1 fi case "$filename" in keywords) full_filename="package.keywords" ;; use) full_filename="package.use" ;; unmask) full_filename="package.unmask" ;; mask) full_filename="package.mask" ;; *) full_filename="$filename" ;; esac fullpaths="$fullpaths $conf_dir/$full_filename" done # return the fullpaths without the extra initial space echo "$fullpaths" | cut -c 2- return 0 } # Edit the configuration file(s) in $PORT_CONF_DIR given by the arguments # using sudoedit. The arguments given should be just the filename, # like "package.keywords", rather than the full path. editconfs() { if [ $# -lt 1 ]; then err "Usage: editconfs FILENAME+" return 1 fi filepaths="$(expandportageconfpaths $@)" if [ $? -ne 0 ]; then err "Error expanding filenames. Exiting." return 1 fi # Open and edit the files one at a time, because I'm not sure that all # editors will be able to open multiple files in one process correctly. for filepath in $filepaths do # Run sudoedit via su as the SUDO_USER so that it is SUDO_USER # that is required to have edit privileges on the file rather # the root user that we are currently running as. su -c "sudoedit '$filepath'" ${SUDO_USER} done return 0 } # Append a line given by $1 to the configuration file(s) in # $PORT_CONF_DIR given the arguments after $1. The filename arguments # should include just the filename, like "package.keywords", # rather than the full path. The line to be added does not need to # be terminated with a newline character, as it will be added automatically. # # Before appending to a file, the file will be backed up to a file with the same # name plus ".old" in $PORT_CONF_DIR/bak, which will be created if necessary. appendconfs() { if [ $# -lt 2 ]; then err "Usage appendconfs \"line of conf text\" FILENAME+" return 1 fi bak_dir="$PORT_CONF_DIR/bak" appendline="$1" shift filepaths="$(expandportageconfpaths $@)" if [ $? -ne 0 ]; then err "Error expanding filenames. Exiting." return 1 fi [ -d "$bak_dir" ] || mkdir -p "$bak_dir" if [ $? -ne 0 ]; then err "Unable to create backup dir. Exiting." return 1 fi for filepath in $filepaths do if [ -f "$filepath" ]; then bak_path="$bak_dir/$(basename $filepath).old" cp -f "$filepath" "$bak_path" fi echo "$appendline" >> $filepath done return 0 } grepconfs() { if [ $# -lt 1 ]; then err "Usage: grepconfs REGEX FILENAME+" return 1 fi grep_pat="$1" shift case $# in 0) # search all: include filename in results grep -H -E --color=always "$grep_pat" $PORT_CONF_DIR/package.* ;; 1) # search just the one file: no filename in results grep -h -E --color=always "$grep_pat" $(expandportageconfpaths $@) ;; *) # search multiple filepaths: include filename in results grep -H -E --color=always "$grep_pat" $(expandportageconfpaths $@) ;; esac return $? } if [ $# -eq 0 ]; then usage exit 1 fi case "$1" in -h | --help) usage exit 0 ;; -a | --append) shift 1 appendconfs $@ exit $? ;; -e | --edit) shift 1 editconfs $@ exit $? ;; -g | --grep) shift 1 grepconfs $@ exit $? ;; *) usage exit 1 ;; esac