Cover V14, i04

Article
Listing 1

apr2005.tar

Makeinstall -- Capture File System Changes with Snapshot and Rsync

Julie Wang and Michael Wang

Generally speaking, a software application is just a set of files on the file system. Some applications will automatically start one or more processes -- such as sendmail, Apache, and MySQL -- upon startup. Other applications -- such as Perl, PHP, Python, and shell scripts -- wait to be called.

A big part of Unix systems administration is managing sets of files (such as add, delete, and modify) either directly -- like "vi httpd.conf" -- or indirectly by running front-end programs (such as make install, pkgadd, pkgrm, rpm, useradd).

The first step in understanding and managing an application is to find its fileset. Traditional Unix makes this difficult because files belonging to different packages are intermixed. Once you have installed software with make install, it is difficult to know where the files went, and how to uninstall the software cleanly. The package manager excels in this regard. However, it won't help you if there is no pre-created package, and you will need to create one yourself.

This article will describe using Solaris UFS snapshots and rsync to capture the file system changes and to optionally reverse the changes. This technique can be used to make software packages, to run a command in test mode, and to find out what is happening behind the scene.

The makeinstall script was originally written as a wrapper for make install, identifying installed files for packaging (hence the name). For maximum flexibility and capability, the script is divided it into five steps that can be called to catch the changes to the file systems made by make install or any other command.

How makeinstall Works

In a simple case of replacing an existing file, a prudent sys admin would use the following steps:

cp   foo foo.old # save the old copy
cp   foo.new foo # install the new copy
diff foo.new foo # find what has changed
cp   foo.old foo # optionally to reverse the change
rm   foo.old     # remove the old copy
The five steps in the makeinstall script are similar to the above steps, but are on the file system level. The steps are:

1. Save the old copies of file systems with snapshots.

2. Make the changes.

3. Compare the old and new file systems with rsync.

4. Optionally reverse the changes.

5. Delete the snapshots.

The alternative methods to meet the same goals include gaining intimate knowledge of the software package, using time stamps to find the newly installed or changed files, and working on the chroot environment. Compared with the alternative methods, makeinstall uses brute force, and therefore is simpler and more reliable -- as reliable as snapshot and rsync. The alternative methods can still be used to validate the results.

Basic Script Usage

The makeinstall script is called with four named arguments processed with my_getopts (see Michael Wang's January 2003 article on UnixReview.com: http://www.unixreview.com/documents/s=7781/uni1042138723500/):

fs:=[+-]<list of filesystems>        Default: /:/usr:/var:/opt
snaproot:=<snapshot top mount point> Default: /snapshot
bs:=<backing-store>                  Default: $(< <snaproot>/.backing-store)
step=<step number>                   Default: 0 (do nothing)
The fs option specifies which file systems to make snapshots of. The default is the existing subset of "/", "/usr", "/var", and "/opt". For example, if "/opt" is not a separate file system, it will not be in the list. You can use fs:=+<file system list> to specify additional file systems, use fs:=-<file system list> to remove the file systems from the default list, and fs:=<file system list> to specify alternative file systems. The <file system list> is a list of file systems separated by colons.

The snaproot option specifies the snapshot root directory under which the snapshot file systems are mounted. The default is /snapshot. The snapshot for a file system will be mounted as their original name under this snaproot, except that "/" is replace by "/root" for clarity.

The bs option specifies the common backing store for all the snapshot file systems. Backing store is used to back up the old data when there is a change in the original file system. The backing store should not be in any of the file systems of which you are making snapshots.

The step option specifies which step to run. For example, 1 is to run step 1, and 2 is to run step 2, and so on. 34 is to run step 3 and 4; 9 is to run all the steps; 0 (the default) is to run no steps. For example:

makeinstall fs:=+/apps4 snaproot:=/snapshot bs:=/apps2 step=1
Code Review
Step 1 is to make a backup of all possibly involved file systems. This is like cp foo foo.old, but the temporary backup is done using snapshot. This part is done using a do_snapshot function; the fssnap command hidden inside this function is Solaris specific:

  function do_snapshot {
    typeset fs=${1} sr=${2} bs=${3} mp dv ix
    for ix in ${fs//:/ }; do
      mp=${sr}${ix/%"/"/"/root"}
      mkdir -p $mp

      mount -F ufs -o ro $(fssnap -F ufs -o bs=${bs},unlink $ix) $mp
      fssnap -i -o blockdevname $ix | awk "{print \$NF}" | read dv &&
      df -Pk $mp | grep "^$dv" || return 1
    done
  }
This function takes three arguments: a list of file systems for which to make snapshots, separated by colons; the snapshot root directory for the snapshot mount points; and the backing store directory.

For each of the file systems in the list, the snapshot is created and mounted under the snapshot root directory, under their original names (except "/" file system is mounted as "root"). For example:

do_snapshot /:/usr /snapshot /backing-store
will create snapshots for "/" and "/usr", and mount them as "/snapshot/root" and "/snapshot/usr". The line:

mp=${sr}${ix/%"/"/"/root"}
is a shorter but cryptic way to say:

if [[ $ix == "/" ]]
then mp=${sr}/root
else mp=${sr}${ix}
fi
The last two lines in the for loop check whether the snapshot is successfully created and the file system mounted. If the check fails, the function will immediately return with a non-zero status. This will cause the program to terminate because "errexit" (set -e) is set in the makeinstall script. Because of the nature of this program, we do not want to proceed when there is an error; we do not want to proceed with step 2 if step 1 fails.

Although this is a standalone function, the function itself is not exposed to the user. The user passes arguments to the makeinstall script; the script processes the arguments and calls the function.

Step 2 is a place holder. It simply reminds you to type make install (or other command) for which you want to see the changes it makes on the file systems.

Step 3 is to find what changes have been made. This is done using rsync in "dry-run" mode (-n or --dry-run option), without doing the actual synchronization:

rsync -vanx --delete <source>/ <target>/
As of this writing, there are two issues with the current version of rsync (2.6.3) in dry-run mode related to this application:

1. It does not produce a list of files with changes in owner/group, permission, or time stamp ("Rsync Bug 1764").

2. It does not report empty directories to be transferred to target ("Rsync Bug 1433").

However, this does not affect the majority of the applications of the makeinstall script, as changes in most cases do not fall into these categories.

Step 4 provides the option to reverse the changes. Rsync is run first in dry-run mode from the snapshot file system to the live file system. It then asks for your confirmation for the actual run.

We can think of two situations for which this step is needed. One is if the changes you just made are not desired (e.g., it overwrites your existing files). The other situation is for packaging (e.g., if you just want to find the list of files installed by make install, but do not actually install it).

Step 5 deletes the snapshots. This is a critical step, as the previously saved state of file systems will be gone. Unless you explicitly invoke this step, confirmation is prompted. The undo_snapshot function used in this step is the reverse of the do_snapshot function:

function undo_snapshot {
  typeset fs=${1} sr=${2} mp ix
  for ix in ${fs//:/ }; do
    mp=${sr}${ix/%"/"/"/root"}
    umount $mp
    fssnap -F ufs -d $ix
    ! fssnap -i -o blockdevname $ix | awk "{print \$NF}" | read &&
    ! df -Pk $mp | grep "^/dev/fssnap/[0-9]\{1,\}" || return 1
  done
}
The last two lines in the for loop check whether the snapshot is actually deleted and snapshot file system unmounted. The function fails when either of the conditions is not satisfied.

Case Studies

Case 1: Install Perl module

Frank is a sys admin for a major bank in the United States. He received a request from a developer to install the Perl module "Date::Manip". Frank was not familiar with this Perl module (or Perl modules in general), but he found the command to install the module was:

perl -MCPAN -e 'install Date::Manip'
To be safe, he tried the installation on his own workstation running Solaris with UFS file systems. First, he ran step 1 of makeinstall to create snapshots for: /, /usr, /var, and /opt:
# makeinstall bs:=/apps4 step=1

/dev/fssnap/3   493688     77196    367124   18%   /snapshot/root
/dev/fssnap/2  2055705   1181127    812907   60%   /snapshot/usr
/dev/fssnap/1  2055705     48364   1945670    3%   /snapshot/var
/dev/fssnap/0  1915416     14166   1843788    1%   /snapshot/opt
Then he ran:

perl -MCPAN -e 'install Date::Manip'
to install the module. After this, he ran step 3 to find what files were installed or changed on his workstation:

# makeinstall bs:=/apps4 step=3
This produced a list of files:

/opt/csw/lib/perl/5.8.2/perllocal.pod
/opt/csw/lib/perl/site_perl/auto/Date/Manip/.packlist
/opt/csw/share/man/man3/Date::Manip.3perl
/opt/csw/share/perl/site_perl/Date/Manip.pm
/opt/csw/share/perl/site_perl/Date/Manip.pod
He looked at the files and realized that the perllocal.pod is a shared file with entries for all locally installed modules, and the .packlist contains the list of files in this module. He then distributed the last four files to the developer's workstation -- which was running VxFS and was not configured for CPAN access -- and manually modified perllocal.pod.

Since the installation worked fine, he skipped step 4, which would allow him to reverse the changes, but proceeded to step 5 to delete the snapshots:

# makeinstall bs:=/apps4 step=5
Deleted snapshot 3.
Deleted snapshot 2.
Deleted snapshot 1.
Deleted snapshot 0.
Case 2: Making sendmail packages

Andrew is a sendmail administrator and has Sun's sendmail packages SUNWsndmr and SUNWsndmu installed, but he wants to build the sendmail package from the current source. Andrew knows about makeinstall so he ran the first step to create snapshots for all the operating system file systems. Then he ran make install to install the sendmail. He continued to run step 3 to produce a list of newly installed files. He was glad he used makeinstall, as the files were installed all over the place, mixed with other operating system files:

/etc/mail/helpfile
/etc/mail/statistics
/usr/bin/hoststat -> /usr/lib/sendmail
/usr/bin/mailq -> /usr/lib/sendmail
/usr/bin/newaliases -> /usr/lib/sendmail
/usr/bin/purgestat -> /usr/lib/sendmail
/usr/bin/vacation
/usr/lib/sendmail
/usr/lib/smrsh
/usr/sbin/editmap
/usr/sbin/mailstats
/usr/sbin/makemap
/usr/sbin/praliases
/usr/share/man/cat1/mailq.1
/usr/share/man/cat1/newaliases.1
/usr/share/man/cat1/vacation.1
/usr/share/man/cat5/aliases.5
/usr/share/man/cat8/editmap.8
/usr/share/man/cat8/mailstats.8
/usr/share/man/cat8/makemap.8
/usr/share/man/cat8/praliases.8
/usr/share/man/cat8/sendmail.8
/usr/share/man/cat8/smrsh.8
He saved the files. He wanted to create a new package to replace Sun's packages, so he reversed the changes by running step 4. He verified the integrity of Sun's original packages with pkgchk, and then deleted the snapshots.

Case 3: Install CSW openssh package

Janet wants to replace Sun's ssh package with a newer version. She knows and likes the Blastwave distribution of Solaris software packages (http://www.blastwave.org/). Janet downloaded the openssh package and examined the file list contained in this package using pkgchk -l. She confirmed that the package does not conflict with Sun's package.

Janet knows about makeinstall and wanted to compare the fileset using both methods. Upon comparison of both lists, Janet found these differences:

1. Makeinstall indicated that the files /etc/passwd and /etc/shadow were updated. She looked at the files and found that "sshd nonpriv userid" sshd was created. Upon further investigation, she found a useradd command in the preinstall script. Files created and modified in preinstall and postinstall scripts are not in the pkginfo list, but are captured by the catch-all makeinstall script with brute force. Janet then researched userid and learned about privilege separation.

2. Makeinstall also reported that /opt/csw/etc/sshd_config was created but was not in the pkginfo list. Janet found this file was generated in the postinstall script:

cp -p $CONFDIR/$CONF_FILE.CSW $CONFDIR/$CONF_FILE
3. Makeinstall indicates the following files are installed or modified:

/var/sadm/install/contents
/var/sadm/pkg/CSWossh/install/copyright
/var/sadm/pkg/CSWossh/install/depend
/var/sadm/pkg/CSWossh/install/preremove
/var/sadm/pkg/CSWossh/pkginfo
Again, this is because makeinstall reports what actually happened. Thus, Janet learned how Solaris software packaging works.

Makeinstall also caught that after removing the package, the userid "sshd" created in the installation remained. The package removal should restore the system to its state prior to package installation, whenever possible.

Case 4: Run Oracle's root.sh

Ben is a sys admin and was asked to run "root.sh" as superuser as part of an Oracle software installation. What does root.sh do? Is it safe to run? He looked at root.sh and had an idea what it would do, but decided to use makeinstall to find the actual changes.

Because Oracle software is installed on /myapp-ds1, which is not part of the operating system file systems, he specified an additional file system on the makeinstall command line:

# makeinstall step=1 fs:=+/myapp-ds1 bs:=/apps2 snaproot:=/snapshot
Then he proceeded to step 2 to run root.sh, shown here:

  # ./root.sh
  Running Oracle9 root.sh script...
  
  The following environment variables are set as:
    ORACLE_OWNER=oracle
    ORACLE_HOME=/myapp-ds1/ora01/app/oracle/product/9.2.0
  
  Enter the full pathname of the local bin directory: [/usr/local/bin]:
    Copying dbhome to /usr/local/bin ...
    Copying oraenv to /usr/local/bin ...
    Copying coraenv to /usr/local/bin ...

  Adding entry to /var/opt/oracle/oratab file...
  Entries will be added to the /var/opt/oracle/oratab file as needed by
  Database Configuration Assistant when a database is created
  Finished running generic part of root.sh script.
  Now product-specific root actions will be performed.
Next, he ran step 3 to find the changes made to the OS file systems and on Oracle's own file system. He summarized the changes as follows:

1. Three Oracle-provided utilities were copied to a local binary directory:

/usr/local/bin/coraenv
/usr/local/bin/dbhome
/usr/local/bin/oraenv
2. "oratab" was created:

/var/opt/oracle/oratab
3. Three files were installed in /opt area:

/opt/ORCLfmap/bin/fmputl
/opt/ORCLfmap/bin/fmputlhp
/opt/ORCLfmap/etc/filemap.ora
4. File permissions were changed for many Oracle files, for example:

/myapp-ds1/ora01/app/oracle/product/9.2.0/bin/*
5. setuid root for the following two files:

/myapp-ds1/ora01/app/oracle/product/9.2.0/bin/dbsnmp
/myapp-ds1/ora01/app/oracle/product/9.2.0/bin/oradism
Changes 4 and 5 were discovered with the patched rsync code due to the rsync bug 1764 mentioned earlier ("Rsync Bug 1764").

Ben supports a failover cluster with the Oracle software and database installed on storage shared among the nodes. Upon the node failure, the Oracle file systems are unmounted from the failed node and mounted on another node within the cluster. The Oracle instance and Sqlnet listener are started on the new node.

Will the locally installed files affect the failover capability? Will the setuid files present a security issue? Ben presented these issues to the database group.

Conclusion

Makeinstall uses the "when in doubt, use brute force" technique to find the file system changes after running a command. The program can be used for software packaging, test run, and educational purpose. Makeinstall is written for UFS on Solaris. However, the idea can be extended to other systems.

The entire program is shown in Listing 1. Future versions of this program, if any, can be found at: http://www.unixlabplus.com/unix-prog/makeinstall/.

References

"Processing Command-line Arguments with my_getopts" by Michael Wang -- http://www.unixreview.com/documents/s=7781/uni1042138723500/

"Rsync Bug 1433." Samba Team. Retrieved 13 Sep 2004 -- https://bugzilla.samba.org/show_bug.cgi?id=1433

"Rsync Bug 1764." Samba Team. Retrieved 12 Jan 2005. -- https://bugzilla.samba.org/show_bug.cgi?id=1764

"Rsync Web Pages." The rsync team. Retrieved 9 Sep 2004 -- http://rsync.samba.org/

Julie Wang works for Independence Air (http://www.flyi.com/). She manages Oracle databases, Unix operating systems, Lawson enterprise systems, among other systems. She can be reached at: Julie.Wang@flyi.com.

Michael Wang is Julie's husband. He can be reached at: xw73@columbia.edu.