Cover V11, I09

Article
Figure 1
Figure 2
Figure 3
Figure 4

sep2002.tar

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.