The
Ol' Switcharoo
David Sweet
As a consultant, I am often asked to write custom scripts. A common
request is to write a script that changes the state of a system.
The "state of a system" is an elusive, all-encompassing
concept, but I use it to describe a configuration that allows a
specific use of a machine. Changing the state of a system can be
as simple as modifying a single file like /etc/nologin or
/etc/shadow to enable or disable the login state. Sometimes
the change is more complex, requiring modification of the entire
identity of a system by swapping out all the files mentioned in
the Solaris man page for sys-unconfig.
I recently worked at Brigham and Woman's Hospital (BWH) in
Boston, where one task was to upgrade all the systems to Solaris
8. My solution used a Jumpstart server, finishing scripts, and an
init script that made sure a system was not only pristine at installation
time but also re-synchronized to the Jumpstart image each time the
system was booted. (A Jumpstart image actually refers to the installer
cd image, but I have expanded the term to include the tasks
performed by the Jumpstart finishing scripts.) It was essential
that there were no workstations with unique configurations that
would complicate administrative tasks.
The client requested that one system be network independent some
of the time, because the system would be used during brain surgery
and network outages could bring the system to its knees1. Thus,
I needed a script to switch the system from a network-dependent
state to a network-independent state. In the network-dependent state,
the system would benefit from the Jumpstart solution. In the network-independent
state, the network would not affect surgery in any way.
The script would have to swap out a lot of files, and it was possible
that more files would need to be added later to the list of swapped
files. Therefore, I decided the script's configuration would
be dynamic. The script I created is known as "switcharoo".
(All code for this article is available at: http://www.sysadminmag.com/code/.)
The idea behind switcharoo is that once it is installed and configured,
you can execute it with no arguments to see which state the system
is in, and then you need only one argument to change the system
to the specified state:
# switcharoo
netdependent state is active
# switcharoo netindependent
state is now netindependent
#
Switcharoo was written in Bourne shell for Solaris but should work
on any UNIX operating system. (I have tested it on Mac OS X and FreeBSD.)
Switcharoo provides a ready-made solution to use in situations where
a client needs a custom script to switch between system states.
"State", as it is used in this article, is central to
switcharoo's functionality. A state is an arbitrary number
of files whose contents or lack of existence makes a system useful
in a particular way. Switching states can change the contents of
files, remove files, or add files to the system, thus making the
system useful in a different way. In the BWH example, I created
two states -- one state made the system useful by allowing it
full access to the network (netdependent), and the other state made
the system useful by making it unaffected by network outages (netindependent).
Installation
Switcharoo is installed by simply making it executable and placing
it somewhere in your path:
# chmod a+x switcharoo
# mv switcharoo /usr/local/bin
#
You may need to modify switcharoo's header:
#!/bin/sh
# configuration specific variables
###############################################################
CONFIGDIR="/etc/'basename $0'"
FILEDIR="${CONFIGDIR}/files"
SCRIPTDIR="${CONFIGDIR}/scripts"
STATEFILE="${CONFIGDIR}/state.txt"
ALLSTATES="${CONFIGDIR}/states.txt"
CONFIGFILE="${CONFIGDIR}/config.txt"
LOCKFILE="${CONFIGDIR}/lock"
ACCESSCMD="/usr/local/bin/accessinfo"
DEFAULTSHELL="/bin/sh"
DEFAULTEDITOR="vi"
Like all shell scripts, the first line should point to the path of
the appropriate shell. Bourne shell (sh) can usually be found
in /bin. The default value of CONFIGDIR is dynamic and
specifies the location of all the files and directories associated
with switcharoo's configuration. If you don't change the
file name of the switcharoo script, CONFIGDIR is effectively
set to /etc/switcharoo, since Bourne sets $0 to the
path of the executed script. I will explain why this can be handy
later.
Other variables you might want to change are ACCESSCMD,
DEFAULTSHELL, and DEFAULTEDITOR. If you choose not
to compile and install accessinfo, then make ACCESSCMD
blank. Switcharoo can also be configured to execute shell scripts
before or after a state change. DEFAULTSHELL defines which
shell to use for these scripts and DEFAULTEDITOR defines
which editor will be used for editing these scripts.
You probably do not want to change their values, but you might
be interested in what STATEFILE, ALLSTATES, CONFIGFILE,
and FILEDIR refer to. STATEFILE stores the name of
your system's present state. ALLSTATES stores all the
state names that have been defined. CONFIGFILE associates
full file paths to the states to which they belong and to the appropriate
arbitrarily named files in switcharoo storage. FILEDIR is
the directory that serves as switcharoo's storage.
The accessinfo utility solves certain permission-related issues.
Problems can arise because switcharoo uses cp to swap files,
which causes the new file to inherit the permissions of the file
being overwritten. You will not need accessinfo if the files being
swapped have the same permissions, but compiling and installing
accessinfo is simple:
# cc -o accessinfo accessinfo.c
# mv accessinfo /usr/local/bin
#
Accessinfo is a useful tool on its own -- it takes one argument,
a file path, and returns the numeric values for the files mode, uid,
and gid:
# accessinfo .
40755 501 20
#
The returned values can be fed directly into chmod, chown, and chgrp.
Don't worry if the mode looks strange. The first digit specifies
the file's type. Chmod will accept the whole value but will ignore
the first digit because it refers to a read-only value. The following
example demonstrates accessinfo's usefulness:
# ls -l
total 0
drwxr-xr-x 2 root daemon 24 Mar 8 01:17 test1
-rw-r--r-- 1 dsweet wheel 0 Mar 8 01:17 test2
# accessinfo test1
40755 0 1
# accessinfo test2
100644 501 0
# chmod 40755 test2
# chown 0 test2
# chgrp 1 test2
# ls -l
total 0
drwxr-xr-x 2 root daemon 24 Mar 8 01:17 test1
-rwxr-xr-x 1 root daemon 0 Mar 8 01:17 test2
#
The distribution comes with a man page. The man page contains more
information about switcharoo so you might as well install it, but
it's less user-friendly than this article since it is a man page.
Move switcharoo.1 into a man1 directory somewhere in your MANPATH
to install it:
# echo $MANPATH
/usr/local/share/man:/usr/share/man:/usr/X11R6/man
# mv switcharoo.1 /usr/local/man/man1
# man switcharoo
man: Formatting manual page...
SWITCHAROO(1) SWITCHAROO(1)
NAME
switcharoo - switches between states
...
Configuration
All the necessary files and directories that switcharoo needs
are created as per the paths specified in the script's header
the first time switcharoo is run:
# switcharoo
switcharoo file(s) and/or directory(s) being created:
/etc/switcharoo directory created
/etc/switcharoo/files directory created
/etc/switcharoo/scripts directory created
/etc/switcharoo/state.txt file created
/etc/switcharoo/states.txt file created
/etc/switcharoo/config.txt file created
No state is active
#
The next step is to define a state. You can define an arbitrary number
of states. If you know how many states you are going to eventually
create, it's best to create them all immediately, but you can
add states later:
# switcharoo create netdependent
netdependent state created
# switcharoo create netindependent
netindependent state created
#
If you need to remove a state definition later, just use the destroy
argument instead:
# switcharoo destroy netindependent
netindependent state destroyed
#
Since the whole point of switcharoo is to swap in and out files, the
next step is to associate files to the specific states:
# switcharoo add all /etc/passwd
/etc/passwd has been added to state netdependent
/etc/passwd has been added to state netindependent
# switcharoo add all /etc/shadow
/etc/shadow has been added to state netdependent
/etc/shadow has been added to state netindependent
# switcharoo add all /etc/group
/etc/group has been added to state netdependent
/etc/group has been added to state netindependent
# switcharoo add all /etc/nsswitch.conf
/etc/nsswitch.conf has been added to state netdependent
/etc/nsswitch.conf has been added to state netindependent
# switcharoo add all /etc/motd
/etc/motd has been added to state netdependent
/etc/motd has been added to state netindependent
# switcharoo add netdependent /etc/auto_master
/etc/auto_master has been added to state netdependent
# switcharoo add netdependent /etc/rc3.d/S99sync_mech
/etc/rc3.d/S99sync_mech has been added to state netdependent
# switcharoo add netindependent /etc/nologin
/etc/nologin has been added to state netindependent
#
Files that should be swapped between the states are added to a special
state definition called "all". When a file is added to "all",
a copy of the file is made for each state that presently exists and
the copy is put in switcharoo storage. At that point, the stored files
are identical. The stored files only become different from each other
when a file is edited and the system is later switched to another
state. Note that if a state is created later, files added to "all"
will not be part of its definition. The last three commands add files
to just one state. If a file is not added to all states, then the
file will be removed when entering the state where the file is not
defined.
If you are a Solaris admin, you can see why the above files are
being added, except for S99sync_mech. (The above example
comes directly from BWH's configuration.) S99sync_mech
is the init script that synchronizes the system to the Jumpstart
image and this obviously should not be done in a network-independent
state. To simplify things, Figure 1 shows the file system activity
resulting from issuing the above command stream.
Like create, add has its inverse argument. Use the
delete argument to remove a file from a state definition:
# switcharoo delete netdependent /etc/rc3.d/S99sync_mech
/etc/rc3.d/S99sync_mech removed from state netdependent
#
Adding files to a state simply tells the system that the files should
be changed when a state is changed. You'll still need to differentiate
the files between the two states, one at a time. The files as they
are when switcharoo is installed will probably match what you want
one of the system states to be like. In the BWH example demonstrated
so far, the files matched the netdependent state, and therefore no
further changes were required to those files. However, I had to edit
the netindependent files in order to create a state that was actually
netindependent. To do this, I typed "switcharoo netindependent".
This generates the filesystem activity shown in Figure 2.
With the netdependent versions of the files stored, I edited all
the netindependent versions of the files. I didn't edit the
netindependent files in switcharoo storage because they would be
overwritten by the real files when I later switched the state back
to netdependent (Figure 3). In fact, I recommend that you never
mess with the files in the switcharoo configuration directory; let
switcharoo do its job. If I later discovered that some "netdependent"
files needed to be modified, I'd change the state again and
edit once more.
The final configuration step is to create pre- and post-state
change scripts. This is not always necessary, but the client requested
that switcharoo remind the user to reboot the system after making
the state change. The script argument drops the user into an editor
and allows him to modify the script that will be executed just prior
to or just after the specified state change:
switcharoo script pre|post [state]
The editor you are dropped into is determined by the EDITOR
environment variable or the default specified in switcharoo's
header. The first time you edit a pre- or post-state change script,
you will be editing a file similar to:
#!/bin/sh
# netdependent state's post-state-change script
If you have any questions about when these state change scripts are
run, refer to Figure 4.
Implementation
Once switcharoo is installed and configured, you will want to
be reminded of the configuration, which is available with the "report"
argument. Report displays information about the present state if
no arguments follow it:
# switcharoo report
netdependent:
/etc/passwd
/etc/shadow
/etc/group
/etc/nsswitch.conf
/etc/motd
/etc/auto_master
/etc/rc3.d/S99sync_mech
a post-state-change script will be executed
#
Alternately, you may give report a previously defined state
as an argument or the special state named all.
If one system needs to use switcharoo to switch between network
configurations and to switch between development environments, you
can create four states like the following:
net+build1
net+build2
nonet+build1
nonet+build2
A better way to do this is to utilize the dynamic definition of CONFIGDIR.
Because it uses the basename of the executed script as part of its
path, you can create a link from switcharoo to a new command name.
The link, which is effectively a new command, will create an independent
file structure to store its configuration information:
# ln -s switcharoo netdependent
# ln -s switcharoo buildenv
# netdependent create on
switcharoo file(s) and/or directory(s) being created:
/etc/netdependent directory created
/etc/netdependent/files directory created
/etc/netdependent/scripts directory created
/etc/netdependent/state.txt file created
/etc/netdependent/states.txt file created
/etc/netdependent/config.txt file created
on state created
state is now on
# netdependent create off
off state created
# buildenv create 1
switcharoo file(s) and/or directory(s) being created:
/etc/buildenv directory created
/etc/buildenv/files directory created
/etc/buildenv/scripts directory created
/etc/buildenv/state.txt file created
/etc/buildenv/states.txt file created
/etc/buildenv/config.txt file created
1 state created
state is now 1
# buildenv create 2
2 state created
#
After adding all the files and editing the post-state change scripts,
the report looks something like this:
# netdependent report all
on state is active
on:
/etc/passwd
/etc/shadow
/etc/group
/etc/nsswitch.conf
/etc/motd
/etc/auto_master
/etc/rc3.d/S99sync_mech
a post-state-change script will be executed
off:
/etc/passwd
/etc/shadow
/etc/group
/etc/nsswitch.conf
/etc/motd
/etc/nologin
a post-state-change script will be executed
#
Utilizing the dynamic nature of CONFIGDIR in this way dramatically
increases the functionality of switcharoo. I recommend only using
differently named switcharoos when the states they control don't
access the same files. The only real risk is confusion -- if buildenv
made a change to a file also controlled by netdependent, then
the modified file would become part of the previous netdependent
state when netdependent switched states.
Switcharoo can be implemented in many different ways. It can do
more than just switch between system-wide configurations. A user
may want to switch between shell configurations:
# switcharoo create local
local state create
# switcharoo create remote
remote state create
# switcharoo add all /home/dsweet/.login
/home/dsweet/.login has been added to state local
/home/dsweet/.login has been added to state remote
# switcharoo add all /home/dsweet/.cshrc
/home/dsweet/.cshrc has been added to state local
/home/dsweet/.cshrc has been added to state remote
#
If you later wanted to give this ability to all the accounts on a
system, then you could extend the dynamic nature of CONFIGDIR
one step further:
CONFIGDIR="${HOME}/etc/'basename $0'"
When CONFIGDIR is set like this, users can switch between personally
defined states without stepping on other user's configurations.
Note that switcharoo has no built-in functionality to allow users
to modify files to which they would not normally have access. Therefore,
this is not a security risk.
Switcharoo could be used as the failover script for a high availability
(HA) solution. An HA solution monitors services and, when it detects
a service outage on one system, it needs to start that service on
another system. Often the new system must take on the persona of
the system with the dead service. Switcharoo is quite capable of
swapping the necessary files and then starting the necessary service
in a post-state change script. Furthermore, a pre-state change script
could be used to gracefully shut down the misbehaving service.
I find that the version control software out there is unnecessarily
complex for a project written by a single developer. I use switcharoo
for the version control. I have created a link from switcharoo to
savearoo, and then created savearoo states like "v1.0b1"
and "v1.0b2" and so on. In this way, I actually use switcharoo
to track the version history of switcharoo.
Conclusion
Despite the simple task that switcharoo performs, I am amazed
at how difficult it is to describe. Once someone "gets it",
however, they immediately discover a new way to implement it. Switcharoo
is simple to use, but powerful because of its flexibility. I hope
you find it useful, too.
David Sweet has seven years experience as a UNIX systems administrator
and works as a consultant for Collective (http://www.colltectivetech.com).
David can be contacted at: dsweet@tgd-inc.com. David would
like to thank his peer reviewers Shannon Appelcline and Thomas Montague.
Footnote
1 I was working for BWH's Surgical Planning Lab. The lab
does some amazing things and I encourage you to read about them
at http://splweb.bwh.harvard.edu. The machine I developed
switcharoo for is connected to plasma monitors that hang over the
surgical field of an open MRI machine. Slicer, software developed
for BWH, is used to produce three-dimensional images of the two-dimensional
slices produced by the MRI in near real time.
|