#!/root/usr/sbin/perl
#Tag 0x00000f00
#ident "$Revision $"
#
# this is loaded from the miniroot, and the Perl binary only resides
# in the mounted "real" filesystem.

# regenerate HTML by (requires Perl v5.003 or later):
#  pod2html --infile=ConfigMR.pl --outfile=ConfigMR.html


=head1 NAME

ConfigMR - utility functions for roboinst post-miniroot-inst

=head1 DESCRIPTION

This Perl library file is designed to supply some standard usable
functions for system configuration in the miniroot at the end of a
robo-inst.  This can't use standard Perl module form, because the Perl
standard library might not be available (eg: should the system be
configured to NFS mount F</usr/share>, the Perl library resides under
F</usr/share>, and NFS doesn't work in the miniroot).

All pathnames referenced in this document are those as seen in the
miniroot, unless specified otherwise.

=head1 COPYRIGHT

This software is Copyright Silicon Graphics Inc, 1997,1998.

Feel free to use and enhance for your own needs, but keep this
Copyright notice.

=head1 GLOBAL VARIABLES

A few variables are made global for customization, though they are
mostly useful for debugging and informational purposes.

=over 4

=item B<$VERSION>

The RCS version number of this library.

=cut

BEGIN {
    # set the version for version checking
    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
}

=item B<$mountdir>

The mount point of the real filesystem under the miniroot.  This is
F</root> to match what the miniroot does.  It is accessible so that
it can be changed for testing the script.  A common value for
testing might be F</> (be B<really> careful!) or F</tmp/test>.

=cut

$mountdir = $ENV{"SGI_ROOT"};	# value as used in miniroot.
				# global so that it can be overridden

=item B<$custom>

The directory into which the custom configuration files are copied.
This is F</custom> in the miniroot filesystem. For testing, this can
be an arbitrary path (eg: F<$HOME/project/test/custom.3>).

=cut

$custom = $ENV{"SGI_CUSTOM"};	# value as used in miniroot

=item B<$custuser>

The directory (under F</custom>) under which the user customization
scripts are stored. It must be skipped when installing system
customization files. When installing user files, files are first
looked for under F<$custom/$custuser/$user/>. If that directory
doesn't exist, then the files under F<$custom/$custuser/default/>
are copied to the new user's account. The default value for this
directory is F<=passwd=>.

=cut

$custuser = "=passwd=";

=item B<@backupext>

When configuration files supplied in the "/custom" dir would
overwrite an installed file, the installed file will be renamed with
a backup extension. The defaults of I<.O> and I<.N> mean that the
files will be listed from the I<versions changed> command. I<.ORIG>
handles the case that inst has used both.

=cut

@backupext = (".O", ".N", ".ORIG");

=back

=head1 CALLER SCRIPT CAVEATS

Since Perl does not exist in the miniroot, scripts written to use the
functions in this library file must be coded in a special way due to:

=over 4

=item *

The Perl binary in the F</root> filesystem must be used.

=item *

The compiled in library path (I<@INC>) is incorrect, so it must be
overridden.

=item *

The Perl standard library is sharable, so may not be installed in
the F</root> filesystem, and NFS does not work in the miniroot.  So
this library is written to not require the existence of the library
(hence it is not a module, and some parts are coded oddly).

=back

To handle these issues, roboinst or miniroot Perl scripts need to
use the following prologue:


    #!/root/usr/bin/perl
    #
    # Custom configuration script for use in the miniroot
    #
    # Fix up the Perl @INC for use in the miniroot:
    # (assumes the version originally shipped with IRIX
    #  adjust accordingly if Perl is patched or updated)

    BEGIN {
	require 5.003;		# min version; we're shipping 5.004+...
	# this method works on all IRIX/Perl5 releases
	my(@new) = reverse @INC;
	my($dir);
	foreach $dir (@new) {
	    unshift(@INC, "/root".$dir);
	}
        unshift(@INC, "/custom"); # chicken-or-egg: $custom not yet defined
    }

    # by using unshift() instead of assignment, the standard library
    # locations are still there for debugging.

    # NOTE: because the Perl library is in /usr/share, and the system may be
    # configured to NFS mount /usr/share, and NFS mounts won't be active in
    # the miniroot, it would be wise to not use any standard modules in the
    # miniroot, unless you are sure that they will be there...

    require "ConfigMR.pl";

    # for testing only:
    $debug = @ARGV ? 1 : 0;
    $mountdir = shift if @ARGV;
    $custom = shift if @ARGV;


=head1 FUNCTIONS

The following functions are exported as part of this module.  They
are defined in this order to avoid forward references.

=over 4

=item B<chrootcmd @cmd>

Do a I<`chroot $mountdir @cmd`>, checking that the environment is
"safe".  Mostly just checks that an F<_RLD*ROOT> environment
variable, if it exists, contains F</>, otherwise the F<chroot()>ed
command will fail.  The directory F</root> is the directory under
which the normal filesystems is mounted.


=cut

sub chrootcmd
{
    my(@cmd) = @_;

    my($ev);
    foreach $ev (qw(_RLDN32_ROOT _RLD64_ROOT _RLD_ROOT)) {
	if ($ENV{$ev}) {
	    if (! grep(/^\/$/, split(/:/, $ENV{$ev}))) {
		$ENV{$ev} = "/:" . $ENV{$ev};
	    }
	}
    }
    system( "/etc/chroot", $mountdir, @cmd);
}

=item B<chkconfig $item, on|off|query [, force]>

This is a convenience interface to the I<chkconfig> command on the
F</root> filesystem. It takes the normal arguments (plus I<query>,
which does not change the value), and returns the value B<on> or
B<"">. It returns B<undef> if either the $item or the argument is
invalid or non-existent. The optional I<force> argument is needed to
create a new chkconfig item.

=cut

sub chkconfig
{
    my($item, $value, $force) = @_;

    if ($value eq "on" or $value eq "off") {
        if ($force) {
            chrootcmd("/etc/chkconfig", "-f", $item, $value);
        } else {
            chrootcmd("/etc/chkconfig", $item, $value);
        }
    } elsif ($value ne 'query') {
	return undef;
    }
    my($file) = "$mountdir/etc/config/$item";
    open(CHK, "<$file") or return undef;
    my($state) = <CHK>;
    close(CHK);
    return $state eq "on" ? $state : "";
}

=item B<realpath $path>

The mounted real filesystem could have absolute symlinks which, if
followed, will lead to the wrong place in the filesystem. This
function maps them to a valid real path from the miniroot F</>.

Returns the "fixed" path, or undef if there's a problem.

=cut

sub realpath
{
    my($logpath) = @_;

    my($head) = "";
    if (substr($logpath,0,length($mountdir)) eq $mountdir) {
        $logpath = substr($logpath,length($mountdir));
        $head = $mountdir;
    }
    my(@bits) = split(/\//, $logpath);
    shift(@bits) unless $bits[0]; # get rid of leading empty component
    my($fixed) = "/";
    my($linkv);
    while (@bits) {
        while ( -l $mountdir.$fixed) {
            $linkv = readlink ($mountdir.$fixed);
            if (substr($linkv,0,1) eq "/") {
                # absolute symlink replaces current one
                $fixed = $linkv;
            } else {
                last;
            }
        }
        $fixed .= "/" . shift(@bits);
    };

    return $head.$fixed;
}

=item B<cpPasswd [$pwconv]>

Copy the password file from F</custom/passwd>.  If this file
contains a user named I<root>, then it replaces the standard one in
F</root/etc/passwd>, else it is appended to that file.  The original
is saved as F</root/etc/passwd.O>.  If the argument is true, then
it makes a shadow password file.

=cut

sub cpPasswd
{
    my($mkshadow) = @_;

    my($src, $dest) = ($custom."/passwd", $mountdir."/etc/passwd");
    if (-s $src) {
        my($bux);
	open(CPW, "<$src") or do {
	    warn "$src exists but can't read? $!\n"; return undef; };
	my(@lines) = <CPW>;
	close(CPW);
	my($tdir) = substr($dest,0,rindex($dest,"/"));
	system("/bin/mkdir", "-p", $tdir) unless -d $tdir;
	if (! grep(/^root/, @lines)) {
	    # if appending, copy the saved original, first
            foreach $bux (@backupext) {
                next if ( -f "$dest$bux");
                open(DBX, "<$dest$bux") and do {
                    print OUT <DBX>;
                    close(DBX);
                };
                last;
            }
            open(OUT, ">>$dest") or do {
                warn "can't open $dest for append? $!\n"; return undef; };
	} else {
            # if replacing, rename the original first
            foreach $bux (@backupext) {
                next if ( -f $dest.$bux);
                rename($dest, $dest.$bux) if -s $dest;
                last;
            }
            open(OUT, ">$dest") or do {
                warn "can't open $dest for write? $!\n"; return undef; };
        }
	print OUT @lines;
	close(OUT);
	chmod(0444, $dest);
    }

    if ($mkshadow) {
	chrootcmd("/sbin/pwconv");
    }
}

=item B<add2hosts $ip, @hostnames>

Add an entry to the F</root/etc/hosts> file.  If the $hostnames[0]
already exists but different, the existing one is commented out, and
the new one replaces it.  If it doesn't exist, it is added to the
end of the file.  The value as added is returned.  If either the $ip
or the @hostnames is empty, only a lookup is performed.

The return value is a string of the entry as found or added.

This reads the file each time.  For bulk additions, it is better to
copy a complete hosts file (eg: using I<copytree()>).

=cut

sub add2hosts
{
    my($ip, @hosts) = @_;

    my($hostsfile) = "$mountdir/etc/hosts";
    open(A2H, "<$hostsfile") or do {
	warn "can't read $hostsfile? $!\n"; return undef; };
    my(@lines) = <A2H>;
    close(A2H);

    my(@rec) = split(' ', grep(/\b$hosts[0]\b/, @lines));
    my($update);

    if ($ip and @hosts) {
	# potential add/update
	my($match) = $ip eq $rec[0] ? 1 : 0;
	my($i);
	for ($i = 0; $match && $i <= $#hosts; $i++) {
	    $match = grep(/^$hosts[$i]$/, @rec);
	}
	if (! $match) {
	    my($temp) = $hostsfile.".NEW";
	    $update = $ip . "\t" . join(" ", @hosts);
	    open(OUT, ">$temp") or do {
		warn "can't write to $temp? $!\n"; return undef; };
	    for ($i = 0; $i <= $#lines; $i++) {
		if ($lines[$i] =~ /^$hosts[0]$/) {
		    print OUT $update,"\n";
		    print OUT "#" . $lines[$i];
		    $update = "";
		} else {
		    print OUT $lines[$i];
		}
	    }
	    if ($update) {
		print OUT $update,"\n";
	    }
	    close(OUT);
	    my($bak) = $hostsfile . ".ORIG";
	    rename($hostsfile, $bak) unless -s $bak; # only first time
	    rename($temp, $hostsfile);
	}
    } else {
	# query only
	$update = shift(@rec) . "\t@rec";
    }
    return $update;
}

=item B<mergeHostFile $file>

Merge the contents of F<$file> (assumed to be in hosts file format) with
F</root/etc/hosts> using the rules of I<add2hosts> (above).  It returns
the number of lines merged.  Note that comments in F<$file> are ignored,
and do not make it into F</root/etc/hosts>.

=cut

sub mergeHostFile
{
    my($file) = @_;
    my($count) = 0;

    if ( -f $file ) {
        open(FILE, "<$file") or do {
            warn "can't open $file for read? $!\n"; return undef; };
        my($ip, @names);
        while (<FILE>) {
            next if /^\#/;
            chomp;
            ($ip, @names) = split;
            $count++ if add2hosts($ip, @names);
        }
        close(FILE);
    } else {
        $count = 0;
    }
    return $count;
}

=item B<configNetworking $sys_id, $ypdomain, $netmask, $ip>

This convenience function just forces values for the minimum
networking files: F</etc/sys_id>, F</var/yp/ypdomain>, and
F</etc/config/ifconfig-1.options>.  If $sys_id does not exist in
F</etc/hosts>, then it is added.

All arguments have default values. $sys_id is `hostname`, $ypdomain
and $ip are fetched from F</etc/hosts> (making it the same as the
DNS domain), and $netmask is 0xffffff00. If IP or hostname are not
found, then returns I<undef>, else returns 1.

=cut

sub configNetworking
{
    my($id, $domain, $netmask, $ip) = @_;

    unless ($id) {
        $id = `hostname`;
        chomp($id);
    }
    unless ($netmask) {
        $netmask = '0xffffff00';
    }
    unless ($domain) {
        my(@rec);
        open(HF, "</etc/hosts") or do {
            warn "can't read /etc/hosts? $!\n"; return undef; };
        @rec = split('', (grep(/$id/, <HF>))[0]);
        chomp(@rec);
        close(HF);
        my(@t) = split(/\./,$rec[1]);
        shift(@t);
        $domain = join('.',@t);
        unless ($ip) {
            $ip = $rec[0];
        }
    }
    my($sys_id) = "$mountdir/etc/sys_id";
    my($tdir) = substr($sys_id,0,rindex($sys_id,"/"));
    system("/bin/mkdir", "-p", $tdir) unless -d $tdir;
    open(OUT, ">$sys_id") or do {
	warn "can't write to $sys_id? $!\n"; return undef};
    print OUT "$id\n";
    close(OUT);

    if ($domain) {
        # don't bother unless there is a value for $domain
        my($ypdomain) = "$mountdir/var/yp/ypdomain";
        $tdir = realpath(substr($ypdomain,0,rindex($ypdomain,"/")));
        system("/bin/mkdir", "-p", $tdir) unless -d $tdir;
        open(OUT, ">$ypdomain") or do {
            warn "can't write to $ypdomain? $!\n"; return undef; };
        print OUT "$domain\n";
        close(OUT);
    }

    my($ifconfig) = "$mountdir/etc/config/ifconfig-1.options";
    $tdir = substr($ifconfig,0,rindex($ifconfig,"/"));
    system("/bin/mkdir", "-p", $tdir) unless -d $tdir;
    open(OUT, ">$ifconfig") or do {
	warn "can't write to $ifconfig? $!\n"; return undef; };
    print OUT "netmask $netmask\n";
    close(OUT);

    my(@hosts);
    if ($id =~ /([-\w]+)\./) {
	push(@hosts, $id);
	my($bare) = $1;
	if ($id !~ /$domain/) {
	    push(@hosts, $bare.".".$domain);
	}
	push(@hosts, $bare);
    } else {
	push(@hosts, $id.".".$domain, $id);
    }
    my(@hostent) = add2hosts($ip, @hosts);

    return @hostent ? 1 : undef;
}

=item B<installTree [@dirs]>

Installs a directory tree of files (usually configuration files)
from the F</custom> directory into the F</root> filesystem.  It will
create missing directories, and save existing files aside by
appending F<.ORIG> to the name.  The optional argument(s) are
directories to copy.  By default it copies all directories below
(but not including) the F</custom> directory.

Note that the F</etc/passwd> and F</etc/hosts> file are handled
specially by separate functions.  It is usually more convenient to
create user directories with the supplied special functions.  To
support them, the special dir F</custom/#passwd/> is skipped.

=cut

sub installTree
{
    my(@dirs) = @_;

    my($copied) = 0;
    if (! @dirs) {
	opendir(DIR, $custom) or do {
	    warn "can't opendir($custom)? $!\n"; return undef; };
	@dirs = grep { /^[^\.]/ && -d "$custom/$_" } readdir(DIR);
	closedir(DIR);
    }
    @dirs = grep(!/^$custuser$/, @dirs);
    $copied = cp_rp($custom, $mountdir, @dirs);

    return $copied;
}

# multi-purpose sub

sub cp_rp
{
    my($fromdir, $todir, @dirs) = @_;
    my($copied) = 0;

    my($basewd) = `/bin/pwd`;
    if (@dirs) {
	chdir($fromdir);
	open(FILES, "/sbin/find @dirs -print |") or do {
	    warn "can't open find? !$\n"; return undef; };
        my($src, $dest);
	while(<FILES>) {
	    chomp;
	    $src = $fromdir."/".$_;
	    $dest = realpath($todir."/".$_);
	    if (-d $src) {
		system("/bin/mkdir", "-p", $dest) unless -d $dest;
	    } else {
		if (-s $dest) {
                    my($bu);
                    foreach $bu (@backupext) {
                        unless (-f $dest.$bu) {
                            rename($dest, $dest.$bu);
                        }
                    }
		}
		system("/bin/cp", "-p", $src, $dest);
		$copied++;
	    }
	}
	close(FILES);
	chdir($basewd);
    }
    return $copied;
}

=item B<installSep [$sep]>

Similar to the I<installTree> function, but this one stores the
files in a "flattened" manner, with the directory separator (F</>)
replaced by the F<$sep> character, F<%> by default.  Ie: the file
F</custom/%etc%uucp%Devices> would be installed as the file
F</root/etc/uucp/Devices>.

=cut

sub installSep
{
    my($sep) = @_;

    $sep = "%" unless $sep;
    my($copied) = 0;
    opendir(DIR, $custom) or do {
	warn "can't opendir($custom)? $!\n"; return undef; };
    my(@files) = grep { /^$sep/ && -f $custom."/".$_ } readdir(DIR);
    closedir(DIR);
    my($src, $dest);
    foreach $src (@files) {
	($dest = $src) =~ s,$sep,/,g;
	$dest = $mountdir. realpath("/".$dest);
	$dest =~ s,//,/,g;
	my($tdir) = substr($dest, 0, rindex($dest,"/"));
	if (! -d $tdir) {
	    system("/bin/mkdir", "-p", $tdir);
	}
	if (-s $dest) {
	    rename($dest, $dest.".ORIG");
	}
	system("/bin/cp", $custom."/".$src, $dest);
	$copied++;
    }
    return $copied;
}

=item B<getpwname $name>

This is a home-grown version of I<getpwnam> that will return
something reasonable from F</etc/passwd> even in the presence of NIS
entries, as they would probably choke the system, since NIS isn't
available in the miniroot.

Return values are the same as the standard Perl I<getpwnam> function.

=cut

sub getpwname
{
    my($name) = @_;

    my($passwd) = "$mountdir/etc/passwd";
    open(GPW, "<$passwd") or do {
	warn "can't read $passwd? $!\n"; return undef; };
    my($line) = grep(/^\+?$name:/, <GPW>);
    close(GPW);

    if ($line) {
        chomp($line);
	my(@f) = split(/:/, $line);
	if ($f[0] =~ /^\+(.*)/) {
	    $f[0] = $1;
	}
	return (@f[0,1,2,3],"","",@f[4,5,6]);
    } else {
	return undef;
    }
}

=item X<B<makeauser $username [, $mode [, force]]>>

Create the user account if it doesn't exist:
get the info from F</root/etc/passwd>, then create the home
directory and populate it.  Note that for NIS passwords, all but the
username field is optional.  In this case, no home directory can be
created since NIS is not available in the miniroot.

Add the default user configuration files: Look first for
F</custom/$custuser/$username/filenames>, and if not found, look for
F</custom/$custuser/default/filenames>, where F<filenames> (as a
directory tree) are what is placed in the user's home directory.  If
none of these are found, will use the F</root/etc/std*> files to
create the user's environment.

The optional argument $mode will force the permissions on the user's
home directory.  If not specified, it defaults to I<01755>.  Modes
on the copied files will match those installed into F</custom>.

Normally, this function will not do anything if the user's home
directory allready exists. Setting the optional I<force> argument
will copy the files anyway.

=cut

%uadded=();

sub makeauser
{
    my($username, $mode, $force) = @_;

    return 1 if defined $uadded{$username};
    #
    # create the directory if it doesn't exist
    #
    my($uid, $gid, $home) = (getpwname($username))[2,3,7];
    return undef unless ($uid && $gid && $home);
    my($rhome) = $mountdir . realpath($home);
    $mode = "01755" unless $mode;
    if (! -d $rhome) {
	system("/bin/mkdir", "-p", $rhome);
	chmod(oct($mode), $rhome);
	chown($uid, $gid, $rhome);
    } elsif (! $force) {
        return 0;
    }

    #
    # copy user's configurations.
    # try $username first, then "user".  Note, using shell wildcards
    #
    my($customuser) = ("$custom/$custuser");
    my($copied, @files);

    my($fromdir) = ("$customuser/$username");
    if (! -d $fromdir) {
        $fromdir = "$customuser/default";
    }
    if ( -d $fromdir ) {
        opendir(DIR, $fromdir) or do {
	    warn "can't opendir($fromdir)? $!\n"; return undef; };
	@files = grep { !/^\.\.?$/ } readdir(DIR);
	closedir(DIR);

        $copied = cp_rp($fromdir, $rhome, @files);
    }
    # copy standard .rc files if missing
    if (-f "$mountdir/etc/stdcshrc" && ! -f "$rhome/.cshrc") {
	system("/sbin/cp","$mountdir/etc/stdcshrc","$rhome/.cshrc");
    }
    if (-f "$mountdir/etc/stdlogin" && ! -f "$rhome/.login") {
	system("/sbin/cp","$mountdir/etc/stdlogin","$rhome/.login");
    }
    if (-f "$mountdir/etc/stdprofile" && ! -f "$rhome/.profile") {
	system("/sbin/cp","$mountdir/etc/stdprofile","$rhome/.profile");
    }
    system ("/sbin/chown", "-R", "$uid.$gid", $rhome);
    $uadded{$username} = 1;
    return $copied;
}

=item B<makeusers [$mode]>

Create all of the local users from the F</root/etc/passwd> file, if
their home directory does not exist.  This routine just loops over
F</root/etc/passwd>, calling L</"makeauser"> on each one.

=cut

sub makeusers
{
    my($mode) = @_;
    my($copied);

    my($pwdfile) = $mountdir."/etc/passwd";
    open(GPD, "<$pwdfile") or do {
        warn "hey, where's $pwdfile? $!\n"; return undef; };
    while(<GPD>) {
        my($name, $pas, $uid, $gid, $gcos, $home, $shell) = split(/:/, $_);
        my($rhome) = $mountdir . $home;
        if ($home and $home ne "/" and $home ne "/dev/null" and
	    $uid > 100 and ! -d $rhome) {
            $name =~ s/^\+//;
            makeauser($name, $mode) && $copied++;
        }
    }
    close(GPD);
    return $copied;
}

=item B<movedir $src, $dest [, $nolink]>

This function will move directory trees around the filesystems.
$src and $dest are paths relative to the normal filesystem.  The
default is to leave a symlink pointing from the old location to the
new location, the optional $nolink argument suppresses this.  This
function correctly handles moves within and across mounted
filesystems.  The destination directory may exist, but the
filesystem must be mounted first.

Example 1: move the F</usr/people> directory to F</home>, without
leaving a symlink behind:

    movedir "/usr/people", "/home", 1;

Example 2: move F</opt> to F</usr/share/opt>, and leave a symlink:

    movedir "/opt", "/usr/share/opt";

Returns I<0> on success and I<errno> on failure.

=cut

sub movedir
{
    my($src, $dest, $nolink) = @_;

    my($from) = $mountdir . realpath($src);
    my($to) = $mountdir . realpath($dest);
    my($fdev,$fino,$fmode) = stat($from);
    return $! unless ($fdev);   # srcdir doesn't exist

    my($limb) = $to;
    while (! -d $limb) {
        $limb =~ s,/[^/]*$,,;
    }
    my($tdev,$tino,$tmode) = stat($limb);
    if (! -d $to) {
        system("/sbin/mkdir", "-p", $to);
	return $! unless -d $to;
    }
    if ($tdev == $fdev) {
        opendir(D, $from) or return $!;
        my($d);
        while (defined($d = readdir(D))) {
            next if ($d eq "." or $d eq "..");
            rename($from."/".$d, $to."/".$d) or
                   warn "rename $from/$d to $to/$d? $!\n";
        }
        close(D);
        rmdir($from) or return $!;
    } else {
        system("/bin/find $from/. -depth -print | /bin/cpio -pdm $to");
        system("/bin/rm", "-rf", $from);
    }
    unless ($nolink) {
        symlink($dest, $from) or return $!;
    }
    return 0;
}

=item B<mkNDSslave $master [, @options]>

Make this machine an NDS (Network Dual-head Software) slave.
The arguments are the F<$DISPLAY> of the NDS master, and options to
the F<ndsd> daemon as specified in the F</usr/nds/dh_config> file.

As side-effects, F<nds> is I<chkconfig>'ed on, and it is started from the
F</var/X11/xdm/Xstartup> file.

I<mkNDSslave> returns undef if the NDS configuration files are not
installed, 0 otherwise.

=cut

sub mkNDSslave
{
    my($master,@options) = @_;

    #
    # update the dh_config file
    #
    my($dh_config) = "$mountdir/usr/nds/dh_config";
    my(@dh_mode) = stat($dh_config);
    my($temp) = "$mountdir/usr/nds/dh_backup";

    #
    # not acceptable to die() in the miniroot, so warn() and return;
    #
    open(DHIN, "<$dh_config") or do {
	warn "can't open $dh_config for read? $!\n"; return undef; };
    open(TOUT, ">$temp") or do {
	warn "can't open $temp for write? $!\n"; close(DHIN); return undef; };
    while (<DHIN>) {
	last unless /^#/;
	print TOUT;
    }
    $master .= ":0.0" unless $master =~ /:/;
    print TOUT "-master $master\n";
    my($f);
    foreach $f (@options) {
	print TOUT "$f\n";
    }
    close(DHIN);
    close(TOUT);
    chown(@dh_mode[4,5], $temp);
    chmod($dh_mode[2], $temp);
    rename($dh_config, "$dh_config.O");
    rename($temp, $dh_config);

    #
    # configure the Xstartup file for startup
    #
    my($xstartup) = "$mountdir/var/X11/xdm/Xstartup";
    open(XSIN, "<$xstartup") or do {
	warn "can't open $xstartup for read? $!\n"; return undef; };
    my(@lines) = <XSIN>;
    close(XSIN);
    my($match) = grep(/nds\s+start/, @lines);
    unless ($match) {
	$temp = "$mountdir/var/X11/xdm/temp$$";
	my(@Xmode) = stat($xstartup);
	open(XOUT, ">$temp") or do {
	    warn "can't open $temp for write? $!\n"; return undef; };
	my($i,$n) = (0, $#lines);
	while ($lines[$n] =~ /^\s*$/) {
	    $n--;
	}
	while ($lines[$n] =~ /^\#|^exit/) {
	    $n--;
	}
	for ($i=0; $i <= $n; $i++) {
	    print XOUT $lines[$i];
	}
	print XOUT "#\n# enable NetworkDualhead\n";
	print XOUT "/etc/init.d/nds start silent\n\n";
	for (; $i <= $#lines; $i++) {
	    print XOUT $lines[$i];
	}
	close(XOUT);
	chown(@Xmode[4,5], $temp);
	chmod($Xmode[2], $temp);
	rename($xstartup, "$xstartup.O");
	rename($temp, $xstartup);
    }

    #
    # enable the NDS to start next boot
    #
    chrootcmd("/etc/chkconfig", "nds", "on");

    return 0;
}

1;

__END__

=back

=head1 AUTHOR

Scott Henry <scotth@sgi.com>

=head1 BUGS

The password appending function should arguably do a merge on username.

=cut

# emacs fodder
#
# Local Variables:
# mode: perl
# indent-tabs-mode: t
# wrap-column: 76
# End:
#
