freebsd-ports/sysutils/personality/src/personality
Andreas Klemm 6b34a8a9ea new port of Michael Smith's personality script
personality manages different system configurations
2001-03-25 20:34:50 +00:00

536 lines
14 KiB
Bash

#! /bin/sh
##############################################################################
#
# Copyright (c) 1997 Michael 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.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
#
# $FreeBSD$
#
##############################################################################
#
# This script provides functionality for manipulating collections of
# configuration files which can be organised so as to alter the
# personality of a system.
#
# Initially, the "base" personality is established. This personality
# contains the "reference" copies of configuration files, and is used
# when creating new personalities. The files which are currently
# considered part of the system's personality are those contained in
# the base personality.
#
# A new personality is established by making a copy of the base
# personality under a new name. Each personality maintains a
# separate copy of all configuration files under /etc/personality.
#
# To install a new personality, the files currently in place are
# saved back to the current personality as indicated in
# /etc/personality/current, and the files for the new personality
# copied into place. The 'select' and 'menu' commands which perform
# these installations are implemented in such a fashion as to only
# require the tools available on the root filesystem, so that they
# may be invoked at the earliest stage during system startup.
#
# If the current personality has become damaged, it can be restored
# from the saved copy.
#
# Files can be added to and removed from the personality set. When
# a new file is added, it is copied from the current system into all
# personalities and added to the list file. When a file is removed
# the current version is kept in place, but all copies are removed
# from saved personalities and the file is removed from the list.
#
# XXX To Do :
# Files can be inherited by one personality from another. This is
# simply achieved by copying the relevant files under /etc/personality,
# and into the current system if required.
#
##############################################################################
# Establish some global constants
P_ROOT=/etc/personality
#P_ROOT=/tmp/personality
P_BASE="${P_ROOT}/_base"
P_CURRENT="${P_ROOT}/current"
P_FILES="${P_ROOT}/files"
P_LIST="${P_ROOT}/list"
scriptname="$0"
##############################################################################
# pers_main
#
# Execution begins here after the file has been read.
#
pers_main()
{
case "$1" in
menu)
pers_menu $2 $3
;;
select)
pers_select $2
;;
restore)
pers_restore
;;
save)
pers_save
;;
saveas)
pers_saveas $2
pers_reindex
;;
create)
pers_create $2
pers_reindex
;;
delete)
pers_delete $2
pers_reindex
;;
add)
pers_add $2
pers_reindex
;;
list)
pers_list
;;
remove)
pers_remove $2
pers_reindex
;;
init)
pers_init
pers_reindex
;;
*)
usage
;;
esac
}
##############################################################################
# pers_menu
#
# Present a menu of currently-selectable personalities, assign hotkeys,
# describe the default and optionally go with the default after a timeout
#
pers_menu()
{
# Look and see if there's actually anything to work with
if [ ! -d "${P_ROOT}" ]; then
return
fi
# Pick up a timeout if specified, default to 10 seconds
timeout=10
if [ ! -z "$1" ]; then
timeout="$1"
fi
# Assign a default, if suitable
defpers=""
defname="<none>"
if [ -f "${P_CURRENT}" ]; then
defpers=`cat "${P_CURRENT}"`
defname="${defpers}"
fi
# Loop prompting/reading input until we get a result
while :; do
# Print menu
echo "";
echo "Select System Personality"
echo "========================="
hkey=0
for pers in `cat "${P_LIST}"`; do
echo " ${hkey}) ${pers}"
eval index_${hkey}="${pers}"
hkey=`expr ${hkey} + 1`
done
echo "";
echo " Default : ${defname}"
read -t "${timeout}" -p " Selection : " input
eval selvar=\$index_"${input}"
selpers=""
if [ -z "${input}" ]; then
selpers="${defpers}"
break
elif [ -n "${selvar}" ]; then
selpers="${selvar}"
break
elif [ -d "${P_ROOT}/_${input}" ]; then
selpers="${input}"
break
fi
done
# $selpers now contains the personality we wish to select,
# or is empty if we selected the default when there was none
if [ -z "${selpers}" ]; then
return
fi
# select the personality nominated
pers_select "${selpers}"
}
##############################################################################
# pers_select
#
# Copy the files from the nominated personality out of the repository
# into the real system. Note that this must be able to run with
# nothing other than the contents of /bin available.
#
pers_select()
{
src="${P_ROOT}/_$1";
if [ ! -d "${src}" ]; then
fail "no such personality '$1'"
fi
# Iterate over the file listing, copy them all out
for file in `cat "${P_FILES}"`; do
cp -p "${src}/${file}" "${file}"
done
# Register this personality as being current
echo "$1" > "${P_CURRENT}"
}
##############################################################################
# pers_restore
#
# Reload the configuration files for the current personality, eliminating
# any changes that may have been made.
#
pers_restore()
{
if [ ! -e "${P_CURRENT}" ]; then
fail "no personality currently active"
fi
# Check that the current personality exists
pers=`cat "${P_CURRENT}"`
src="${P_ROOT}/_${pers}"
if [ ! -d "${src}" ]; then
fail "current personality '${pers}' not in the repository!"
fi
# Iterate over the file listing, copy them all out
for file in `cat "${P_FILES}"`; do
cp -p "${src}/${file}" "${file}"
done
}
##############################################################################
# pers_save
#
# If a personality is current, save the current set of files to that
# personality.
#
pers_save()
{
if [ ! -e "${P_CURRENT}" ]; then
fail "no personality currently active"
fi
# Check that the current personality exists
pers=`cat "${P_CURRENT}"`
dest="${P_ROOT}/_${pers}"
if [ ! -d "${dest}" ]; then
fail "current personality '${pers}' not in the repository!"
fi
# OK, go ahead and save stuff. If this fails, we're
# moderately stuffed, so don't worry about it.
for file in `cat "${P_FILES}"`; do
stub=`dirname "${file}"`
mkdir -p "${dest}/${stub}"
cp -p "${file}" "${dest}/${file}"
done
}
##############################################################################
# pers_saveas
#
# Take the currently-active set of configuration files, and save them as
# a new personality, set the new personality as current.
#
pers_saveas()
{
dest="${P_ROOT}/_$1"
if [ -e "${dest}" ]; then
fail "cannot create new personality '$1', name already in use"
fi
# Create the personality directory
mkdir -p "${dest}" || pers_saveas_fail "$1"
# iterate over files to save, copy them in
for file in `cat "${P_FILES}"`; do
stub=`dirname "${file}"`
mkdir -p "${dest}/${stub}"
cp -p "${file}" "${dest}/${file}" || pers_saveas_fail $1
done
# new personality is current
echo "$1" > "${P_CURRENT}"
}
########################################
# pers_saveas_fail
#
# The 'save as' operation failed. Clean
# up and emit a failure message.
#
pers_saveas_fail()
{
rm -Rf "${P_ROOT}/_$1"
fail "could not save current personality as '$1'"
}
##############################################################################
# pers_create
#
# Create a new personality, duplicated from the current base personality
#
pers_create()
{
if [ -e "${P_ROOT}/_$1" ]; then
fail "cannot create new personality '$1', name already in use"
fi
# Ok, duplicate it
cp -Rp "${P_BASE}" "${P_ROOT}/_$1" || pers_create_fail "$1"
}
########################################
# pers_create_fail
#
# An attempt to create a personality failed.
# Clean up and exit with an error message.
#
pers_create_fail()
{
rm -Rf "${P_ROOT}/_$1"
fail "'$1' could not be created"
}
##############################################################################
# pers_delete
#
# Remove a personality from the system. It is legitimate to remove
# the current personality.
#
pers_delete()
{
if [ ! -e "${P_ROOT}/_$1" ]; then
fail "no such personality '$1' to remove"
fi
if [ "$1" = _base ]; then
fail "cannot remove base personality"
fi
# If the requested personality is current, remove the
# reference.
if [ -e "${P_CURRENT}" ]; then
if [ `cat "${P_CURRENT}"` = "$1" ]; then
rm -f "${P_CURRENT}"
fi
fi
# Remove the repository entry
rm -Rf "${P_ROOT}/_$1";
# Make sure it's gone
if [ -e "${P_ROOT}/_$1" ]; then
fail "failed to completely remove personality '$1'";
fi
}
##############################################################################
# pers_add
#
# Add a new file to the system; copy it from the 'real' path into
# each personality directory. Check first to make sure it's not already
# part of the system, and check that the path supplied is absolute.
#
# The file is stored with its full path relative to the repository
# directory.
#
pers_add()
{
if [ ! -r "/$1" ]; then
fail "cannot read '$1' to add to the Personality System"
fi
if [ -e "${P_BASE}/$1" ]; then
fail "file '$1' already part of the Personality System"
fi
if [ ! -f "$1" ]; then
fail "only files can be added to the Personality System"
fi
# looks OK, copy it in
stub=`dirname "$1"`
for targ in ${P_ROOT}/_*; do
mkdir -p "${targ}/${stub}"
cp -p "$1" "${targ}/${stub}" || pers_add_fail "$1";
done
}
########################################
# pers_add_fail
#
# A failure occurred while adding a file to
# the repository; back out any copies that
# made it in and abort with an error.
#
pers_add_fail()
{
for cand in ${P_ROOT}/_*; do
if [ -f "${cand}/$1" ]; then
rm -f "${cand}/$1";
fi
done
fail "'$1' could not be added";
}
##############################################################################
# pers_remove
#
# Remove a file from all personalities in the repository.
#
pers_remove()
{
if [ ! -f "${P_BASE}/$1" ]; then
fail "'$1' is not part of the Personality System";
fi
# OK, it should be there; nuke whatever we can find
for cand in ${P_ROOT}/_*; do
if [ -f "${cand}/$1" ]; then
rm -f "${cand}/$1";
fi
done
}
##############################################################################
# pers_list
#
# List all of the files that comprise the system personality.
#
pers_list()
{
echo "Current personalities:"
for pers in `cat "${P_LIST}"`; do
echo " ${pers}";
done
echo "Files in system personality:"
for file in `cat "${P_FILES}"`; do
echo " ${file}"
done
}
##############################################################################
# pers_init
#
# Initialise the personality collection; refuse to do so if there is
# already one in place, or something else occupying the root path.
#
pers_init()
{
if [ -e "${P_ROOT}" ]; then
fail "cannot initialise, '${P_ROOT}' already exists"
fi
# Create the repository with no files, and no current personality
mkdir -p "${P_ROOT}"
mkdir -p "${P_BASE}"
}
##############################################################################
# pers_reindex
#
# Clean out any empty directories in the repository. This is achieved
# by silently trying to rmdir everything that looks like a directory
# under any personality.
#
# Then rebuild the list of files that comprise the system personality,
# so that the select and menu functions work.
#
pers_reindex()
{
# Remove empty directories
for cand in ${P_ROOT}/_*; do
find -dX "${cand}/." -type d | xargs rmdir >/dev/null 2>&1
done
# Regenerate the files list
find -X "${P_BASE}" -type f | sed "s%${P_BASE}%%" > "${P_FILES}"
# regenerate the personalities list
ls -d "${P_ROOT}/_"* | sed "s%${P_ROOT}/_%%" > "${P_LIST}"
}
##############################################################################
# usage
#
# Emit a (hopefully) helpful diagnostic and exit
#
usage()
{
echo "${scriptname}: incorrect argument(s)"
echo ""
echo " Usage is ${scriptname} <command>, where valid commands are :"
echo " menu [<timeout>] Invoke the menu-driven personality selector"
echo " select <personality> Select a specific personality"
echo " restore Restore the current personality from the saved version"
echo " save Save the current personality"
echo " saveas <personality> Save the current personality under a new name"
echo " create <personality> Create a new personality from the base"
echo " delete <personality> Delete a personality"
echo " add <full path> Add a new file"
echo " remove <full path> Remove a file"
echo " list List all files"
echo " init Initialise the Personality System"
echo ""
exit 2;
}
##############################################################################
# fail
#
# Emit an error message to stderr and exit
#
fail ()
{
echo "${scriptname}: $1";
exit 1;
}
##############################################################################
# Now we have parsed everything, start.
pers_main $1 $2 $3 $4;
exit 0;