From e7a497f89f6badae3da6f4139f60763518927d49 Mon Sep 17 00:00:00 2001
From: Russ Fish <fish@flux.utah.edu>
Date: Mon, 25 Oct 2004 18:10:58 +0000
Subject: [PATCH] First installment of cygwinxp support.

---
 configure                      |   1 +
 tmcd/GNUmakefile.in            |   7 +
 tmcd/cygwinxp/GNUmakefile.in   |  78 ++++
 tmcd/cygwinxp/emount           | 164 ++++++++
 tmcd/cygwinxp/eumount          |  95 +++++
 tmcd/cygwinxp/liblocsetup.pm   | 675 +++++++++++++++++++++++++++++++++
 tmcd/cygwinxp/profile          |  77 ++++
 tmcd/cygwinxp/rc.cygwinxp      |  19 +
 tmcd/cygwinxp/rc.cygwinxp-user |  40 ++
 9 files changed, 1156 insertions(+)
 create mode 100644 tmcd/cygwinxp/GNUmakefile.in
 create mode 100755 tmcd/cygwinxp/emount
 create mode 100755 tmcd/cygwinxp/eumount
 create mode 100644 tmcd/cygwinxp/liblocsetup.pm
 create mode 100755 tmcd/cygwinxp/profile
 create mode 100755 tmcd/cygwinxp/rc.cygwinxp
 create mode 100644 tmcd/cygwinxp/rc.cygwinxp-user

diff --git a/configure b/configure
index 513e86754d..8242e55e06 100755
--- a/configure
+++ b/configure
@@ -1542,6 +1542,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
 	tmcd/linux9/GNUmakefile tmcd/linux9/supfile \
 	tmcd/freebsd5/GNUmakefile tmcd/freebsd5/supfile \
 	tmcd/openbsd/GNUmakefile tmcd/ron/GNUmakefile tmcd/plab/GNUmakefile \
+        tmcd/cygwinxp/GNUmakefile \
 	utils/GNUmakefile utils/vlandiff utils/vlansync utils/delay_config \
 	utils/sshtb utils/create_image utils/node_admin utils/webcreateimage \
 	utils/firstuser utils/export_tables utils/eventping \
diff --git a/tmcd/GNUmakefile.in b/tmcd/GNUmakefile.in
index b1038ad4fd..9add9d6f4a 100644
--- a/tmcd/GNUmakefile.in
+++ b/tmcd/GNUmakefile.in
@@ -29,6 +29,7 @@ CFLAGS	+= -DETCDIR='"$(INSTALL_ETCDIR)"'
 SSLFLAGS = -DWITHSSL 
 TMLIBS	+= -lssl -lcrypto
 SSLOBJ   = ssl.o
+
 ifeq ($(SYSTEM),Linux)
 RHLVERSION    := $(shell cat /etc/redhat-release | sed -e 's/Red Hat Linux release \([0-9]\).*/Linux\1/')
 NEEDKERB := $(shell nm /usr/lib/libssl.a | grep -q krb; echo $$?)
@@ -43,6 +44,7 @@ else
 MDSUBDIR  = linux
 endif
 endif
+
 ifeq ($(SYSTEM),FreeBSD)
 FBSDVERSION    := $(shell uname -v | sed -e 's/FreeBSD \([0-9]\).*/FreeBSD\1/')
 ifeq ($(FBSDVERSION),FreeBSD5)
@@ -52,6 +54,11 @@ MDSUBDIR  = freebsd
 endif
 endif
 
+ifeq ($(SYSTEM),CYGWIN_NT-5.1)
+# Cygwin on  Windows XP (a.k.a. NT 5.1) - resembles the Linux case.
+MDSUBDIR  = cygwinxp
+endif
+
 ifeq ($(EVENTSYS),1)
 	TMCDCFLAGS = `elvin-config --cflags vin4c` \
 		     -I$(TESTBED_SRCDIR)/event/lib -DEVENTSYS
diff --git a/tmcd/cygwinxp/GNUmakefile.in b/tmcd/cygwinxp/GNUmakefile.in
new file mode 100644
index 0000000000..4d3bb00e5c
--- /dev/null
+++ b/tmcd/cygwinxp/GNUmakefile.in
@@ -0,0 +1,78 @@
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+#
+# XXX ONLY RUN THIS INSTALL ON A CYGWIN / WINDOWS XP NODE!
+# Similar to linux9, cygwinxp is an overlay on linux, which is an overlay on common.
+#
+# Trivial. These things just need to be installed into the right place
+# on a testbed node before cutting an image.
+#
+#
+SRCDIR		= @srcdir@
+TESTBED_SRCDIR	= @top_srcdir@
+OBJDIR		= ../..
+SUBDIR		= tmcd/cygwinxp
+
+include $(OBJDIR)/Makeconf
+
+SCRIPTS		= 
+
+#
+# Force dependencies on the scripts so that they will be rerun through
+# configure if the .in file is changed.
+# 
+all:	$(SCRIPTS)
+
+include $(TESTBED_SRCDIR)/GNUmakerules
+
+DESTDIR		=
+SYSETCDIR	= $(DESTDIR)/etc
+ETCDIR		= $(DESTDIR)$(CLIENT_ETCDIR)
+BINDIR		= $(DESTDIR)$(CLIENT_BINDIR)
+VARDIR		= $(DESTDIR)$(CLIENT_VARDIR)
+RCDIR		= $(SYSETCDIR)/rc.d
+INSTALL		= /usr/bin/install -c 
+COMMON		= $(SRCDIR)/../common
+
+install client-install:	baselinux-install common-install etc-install \
+			sup-install script-install bin-install
+	@echo "Remember to install the PEM files if necessary"
+
+simple-install:	common-install script-install bin-install
+
+dir-install:
+
+baselinux-install: dir-install
+	(cd ../linux; $(MAKE) client-install)
+	rm -f $(BINDIR)/rc/rc.healthd
+	rm -f $(BINDIR)/rc/rc.slothd
+
+common-install:	dir-install
+	(cd ../common; $(MAKE) local-install)
+
+sup-install:	dir-install
+
+bin-install:	dir-install
+
+etc-install:	dir-install sysetc-remove sysetc-install
+
+sysetc-install:	dir-install ###ifcfgs
+
+sysetc-remove:
+
+script-install:	dir-install $(SCRIPTS)
+	$(INSTALL) -m 755 $(SRCDIR)/rc.cygwinxp-user $(BINDIR)/rc/rc.cygwinxp-user
+	$(INSTALL) -m 755 $(SRCDIR)/rc.cygwinxp $(BINDIR)/rc/rc.cygwinxp
+	$(INSTALL) -m 755 $(SRCDIR)/liblocsetup.pm $(BINDIR)/liblocsetup.pm
+	$(INSTALL) -m 755 $(SRCDIR)/emount $(BINDIR)/emount
+	$(INSTALL) -m 755 $(SRCDIR)/eumount $(BINDIR)/eumount
+
+sfs-install:
+
+# create ifcfg-eth? files
+ifcfgs:	$(SRCDIR)/mkifcfgs $(SRCDIR)/ifcfg.template
+	$(SRCDIR)/mkifcfgs $(SRCDIR)/ifcfg.template
diff --git a/tmcd/cygwinxp/emount b/tmcd/cygwinxp/emount
new file mode 100755
index 0000000000..1a39a3a1e6
--- /dev/null
+++ b/tmcd/cygwinxp/emount
@@ -0,0 +1,164 @@
+#!/usr/bin/perl -w
+#!/usr/bin/perl -wT
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+use English;
+use Getopt::Std;
+
+#
+# Emulab NFS mount command, called by rc.mounts .  
+#
+# Args are the remote and local mount points, for example:
+#    eumount fs.emulab.net:/q/proj/testbed /proj/testbed
+# With no args, reports the current mounts with "net use".
+#
+# Since this is Windows, mounts go through drive letters, like this:
+#    S:       \\fs.emulab.net\share              # Share
+#    P:       \\fs.emulab.net\q\proj\testbed     # Project 
+#    Q:       \\fs.emulab.net\groups\testbed\TG1 # Group
+#    H:       \\fs.emulab.net\users\fish         # Creator
+#    I:       \\fs.emulab.net\users\mike         # Swapper
+#
+# Creator/swapper information comes from tmcc via TMCREATOR()/TMSWAPPER() files.
+# Any other user mounts require specifying the drive letter with the -d option.
+# 
+# The Services For Unix (SFU 3.5) NFS client commands are used underneath,
+# and CygWin symlinks are made to point to the /cygdrive/driveletter mount,
+# completing the Unix-like illusion.
+
+sub usage()
+{
+    print "Usage: emount [-v] [-d driveletter:] remotehost:path localpath\n";
+    print "   or: emount\n";
+    exit(1);
+}
+my $optlist     = "vd:";
+my $verbose     = 0;
+my $driveletter = "";
+my $remote      = "";
+my $local       = "";
+
+# Drag in path stuff so we can find emulab stuff.
+BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
+
+#
+# Turn off line buffering on output
+#
+$| = 1;
+
+#
+# Load the OS independent support library. It will load the OS dependent
+# library and initialize itself. 
+# 
+use libsetup;
+use liblocsetup;
+
+#
+# Parse command arguments. Once we return from getopts, all that should be
+# left are the positional arguments.
+#
+%options = ();
+if (! getopts($optlist, \%options)) {
+    usage();
+}
+if (defined($options{"v"})) {
+    $verbose = 1;
+}
+if (defined($options{"d"})) {
+    $driveletter = $options{"d"};
+}
+if (@ARGV == 0 ) {
+    system("$NET use");
+    exit(0);
+}
+elsif (@ARGV == 2) {
+    $remote = $ARGV[0];
+    $local = $ARGV[1];
+}
+else {
+    usage();
+}
+
+my $cmd;
+
+# Infer the drive letter from what's being mounted.
+my($project, $group, $user);
+$driveletter = "S:"
+    if (!$driveletter && $remote =~ m|:/share$|);
+$driveletter = "P:"
+    if (!$driveletter && (($project) = ($remote =~ m|:/q/proj/([-[:alnum:]]+)$|)) );
+$driveletter = "Q:"
+    if (!$driveletter && (($project, $group) = 
+	($remote =~ m|:/q/proj/([-[:alnum:]]+)/([-[:alnum:]]+)$|)) );
+if (!$driveletter && (($user) = ($remote =~ m|:/users/([-[:alnum:]]+)|))) {
+    my $tmcreator = TMCREATOR();
+    my $tmswapper = TMSWAPPER();
+    my $creator = `cat $tmcreator`;
+    my $swapper = `cat $tmswapper`;
+    $creator =~ s/\n//;
+    $swapper =~ s/\n//;
+    ##print "user '$user', creator '$creator', swapper '$swapper'\n";
+
+    $driveletter = "H:"
+	if ($user eq $creator);
+    $driveletter = "I:"
+	if (!$driveletter && $user eq $swapper);
+}
+print "Using drive letter $driveletter\n"
+    if ($verbose && $driveletter);
+
+if (!$driveletter) {
+    print STDERR "emount: Must specify a drive letter.\n";
+    exit(1);
+}
+
+# Make sure that mount persistence is off.
+os_noisycmd("$NET use /persistent:no", 0);
+
+# Mount onto a drive letter using the Services For Unix NFS client.
+print "Mounting remote directory '$remote' on drive letter '$driveletter'.\n"
+    if ($verbose);
+$cmd = "$SFCMOUNT $remote $driveletter";
+if (os_noisycmd($cmd, $verbose) != 0) {
+    print STDERR "emount: Failed SFU mount, $cmd.\n";
+    exit(1);
+}
+    
+# Make the CygWin symlink from the local path to the driveletter automount point.
+my $localdir = $local;
+$localdir =~ s|(.*)/.*|$1|;
+my $cygdrive = "/cygdrive/" . lc(substr($driveletter, 0, 1));
+if (length($localdir) && ! -e $localdir) {
+    print "Making CygWin '$localdir' directory to contain symlinks.\n"
+	if ($verbose);
+    if (! os_mkdir($localdir, "0777")) { # Writable so anybody can make symlinks.
+	print STDERR "emount: Failed CygWin mkdir, $cmd.\n";
+	exit(1);
+    }
+}
+if (-e $local) {
+    print "Removing previous CygWin symlink '$local'.\n"
+	if ($verbose);
+    $cmd = "$CHOWN `id -un` $local";
+    if (system($cmd) != 0) {
+	print STDERR "emount: Failed to take ownership of symlink, $cmd.\n";
+    }
+    $cmd = "$RM -f $local";
+    if (system($cmd) != 0) {
+	print STDERR "emount: Failed to remove previous CygWin symlink, $cmd.\n";
+	exit(1);
+    }
+}
+print "Making CygWin symlink '$local' to '$cygdrive'.\n"
+    if ($verbose);
+$cmd = "$LN -f -s $cygdrive $local";
+if (system($cmd) != 0) {
+    print STDERR "emount: Failed CygWin symlink, $cmd.\n";
+    exit(1);
+}
+
+exit(0);
+
diff --git a/tmcd/cygwinxp/eumount b/tmcd/cygwinxp/eumount
new file mode 100755
index 0000000000..82abe51911
--- /dev/null
+++ b/tmcd/cygwinxp/eumount
@@ -0,0 +1,95 @@
+#!/usr/bin/perl -w
+#!/usr/bin/perl -wT
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+use English;
+use Getopt::Std;
+
+#
+# Emulab NFS unmount command, called by rc.mounts .  See emount for more info.
+#
+# Arg is the local mount points, for example:
+#    eumount /proj/testbed
+# 
+# The Services For Unix (SFU 3.5) NFS client commands are used underneath, and the
+# CygWin symlink pointing to the /cygdrive/driveletter mount are cleaned up.
+
+sub usage()
+{
+    print "Usage: eumount [-v] localpath\n";
+    exit(1);
+}
+my $optlist     = "v";
+my $verbose     = 0;
+my $local       = "";
+
+# Drag in path stuff so we can find emulab stuff.
+BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
+
+#
+# Turn off line buffering on output
+#
+$| = 1;
+
+#
+# Load the OS independent support library. It will load the OS dependent
+# library and initialize itself. 
+# 
+use libsetup;
+use liblocsetup;
+
+#
+# Parse command arguments. Once we return from getopts, all that should be
+# left are the positional arguments.
+#
+%options = ();
+if (! getopts($optlist, \%options)) {
+    usage();
+}
+if (defined($options{"v"})) {
+    $verbose = 1;
+}
+if (@ARGV == 1) {
+    $local = $ARGV[0];
+}
+else {
+    usage();
+}
+
+my(%curmounts, $cmd);
+if (os_getnfsmountpoints(\%curmounts) < 0) {
+    fatal("Could not get current NFS mounts!");
+}
+
+if (!defined($curmounts{$local})) {
+    print STDERR "eumount: $local is not mounted.\n";
+	exit(1);
+    }
+else {
+    my $rpath = $curmounts{$local}[0];
+    my $driveletter = $curmounts{$local}[1];
+
+    # Unmount from a drive letter using the Services For Unix NFS client.
+    print "Unmounting remote '$rpath' from drive letter '$driveletter'.\n"
+	if ($verbose);
+    $cmd = "$SFCUMOUNT $driveletter";
+    if (os_noisycmd($cmd, $verbose) != 0) {
+	print STDERR "eumount: Failed SFU umount, $cmd.\n";
+	exit(1);
+    }
+
+    # Kill the CygWin symlink from the local path to the driveletter automount point.
+    print "Removing CygWin symlink '$local'.\n"
+	if ($verbose);
+    $cmd = "$RM $local";
+    if (system($cmd) != 0) {
+	print STDERR "emount: Failed to remove CygWin symlink, $cmd.\n";
+	exit(1);
+    }
+}
+
+exit(0);
+
diff --git a/tmcd/cygwinxp/liblocsetup.pm b/tmcd/cygwinxp/liblocsetup.pm
new file mode 100644
index 0000000000..8bafebac2b
--- /dev/null
+++ b/tmcd/cygwinxp/liblocsetup.pm
@@ -0,0 +1,675 @@
+#!/usr/bin/perl -wT
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+#
+# Linux specific routines and constants for the client bootime setup stuff.
+#
+package liblocsetup;
+use Exporter;
+@ISA = "Exporter";
+@EXPORT =
+    qw ( $CP $LN $RM $CHOWN $EGREP 
+	 $NFSMOUNT $UMOUNT $SFCMOUNT $SFCUMOUNT $NTS $NET
+	 $TMPASSWD $SFSSD $SFSCD $RPMCMD
+	 os_account_cleanup os_ifconfig_line os_etchosts_line
+	 os_setup os_groupadd os_useradd os_userdel os_usermod os_mkdir
+	 os_ifconfig_veth
+	 os_routing_enable_forward os_routing_enable_gated
+	 os_routing_add_manual os_routing_del_manual os_homedirdel
+	 os_groupdel os_getnfsmounts os_getnfsmountpoints os_noisycmd
+	 os_fwconfig_line os_fwrouteconfig_line
+       );
+
+# Must come after package declaration!
+use English;
+
+# Load up the paths. Its conditionalized to be compatabile with older images.
+# Note this file has probably already been loaded by the caller.
+BEGIN
+{
+    if (-e "/etc/emulab/paths.pm") {
+	require "/etc/emulab/paths.pm";
+	import emulabpaths;
+    }
+    else {
+	my $ETCDIR  = "/etc/rc.d/testbed";
+	my $BINDIR  = "/etc/rc.d/testbed";
+	my $VARDIR  = "/etc/rc.d/testbed";
+	my $BOOTDIR = "/etc/rc.d/testbed";
+    }
+}
+
+#
+# Various programs and things specific to Linux and that we want to export.
+# 
+$CP		= "/bin/cp";
+$LN		= "/bin/ln";
+$RM		= "/bin/rm";
+$CHOWN		= "/bin/chown";
+$EGREP		= "/bin/egrep -q";
+
+# Emulab wrappers for Windows.
+$NFSMOUNT	= "$BINDIR/emount";
+$UMOUNT		= "$BINDIR/eumount";
+
+$MKPASSWD	= "/bin/mkpasswd";
+$MKGROUP	= "/bin/mkgroup";
+$AWK		= "/bin/gawk";
+
+$CYGMOUNT	= "/bin/mount";
+$CYGUOUNT	= "/bin/umount";
+
+$SFC		= "/cygdrive/c/SFU/common";
+$SFCMOUNT	= "$SFC/mount";
+$SFCUMOUNT	= "$SFC/umount";
+
+$NTS		= "/cygdrive/c/WINDOWS/system32";
+$NET		= "$NTS/net";
+$BASH		= "/bin/bash";
+
+#
+# These are not exported
+#
+my $USERADD     = "/usr/sbin/useradd";
+my $USERDEL     = "/usr/sbin/userdel";
+my $USERMOD     = "/usr/sbin/usermod";
+my $GROUPADD	= "/usr/sbin/groupadd";
+my $GROUPDEL	= "/usr/sbin/groupdel";
+my $IFCONFIGBIN = "/sbin/ifconfig";
+my $IFCONFIG    = "$IFCONFIGBIN %s inet %s netmask %s";
+my $IFC_1000MBS  = "1000baseTx";
+my $IFC_100MBS  = "100baseTx";
+my $IFC_10MBS   = "10baseT";
+my $IFC_FDUPLEX = "FD";
+my $IFC_HDUPLEX = "HD";
+my @LOCKFILES   = ("/etc/group.lock", "/etc/gshadow.lock");
+my $MKDIR	= "/bin/mkdir";
+my $GATED	= "/usr/sbin/gated";
+my $ROUTE	= "/sbin/route";
+my $SHELLS	= "/etc/shells";
+my $DEFSHELL	= "/bin/tcsh";
+
+#
+# OS dependent part of cleanup node state.
+# 
+sub os_account_cleanup()
+{
+    unlink @LOCKFILES;
+
+    # Generate the CygWin password and group files from the registry users.
+    # Make root's UID zero.
+    printf STDOUT "Resetting passwd and group files\n";
+    if (system("$MKPASSWD -l | \
+            $AWK -F: '/^root:/{\$3=\"0\"} {OFS=\":\"; print}' > /etc/passwd") != 0) {
+	print STDERR "Could not generate /etc/password file: $!\n";
+	return -1;
+    }
+    # Make wheel an alias for Administrators.
+    if (system("mkgroup -l | \
+           $AWK '/^Administrators:/{print \"wheel\" substr(\$0, index(\$0,\":\"))} \
+                {print}' > /etc/group") != 0) {
+	print STDERR "Could not generate /etc/group file: $!\n";
+	return -1;
+    }
+    
+    return 0;
+}
+
+#
+# Generate and return an ifconfig line that is approriate for putting
+# into a shell script (invoked at bootup).
+#
+sub os_ifconfig_line($$$$$$$;$$)
+{
+    my ($iface, $inet, $mask, $speed, $duplex, $aliases,
+	$iface_type, $settings, $rtabid) = @_;
+    my ($miirest, $miisleep, $miisetspd, $media);
+    my ($uplines, $downlines);
+
+    #
+    # Special handling for new style interfaces (which have settings).
+    # This should all move into per-type modules at some point. 
+    #
+    if (defined($settings) && exists($settings->{"protocol"}) &&
+	$settings->{"protocol"} ne "ethernet") {
+
+	#
+	# Setting the protocol is special and appears to be card specific.
+	# How stupid is that!
+	#
+	my $protocol = $settings->{"protocol"};
+	my $privcmd  = "";
+	
+	if ($iface_type eq "ath") {
+	    $privcmd = "/sbin/iwpriv $iface mode ";
+
+	    SWITCH1: for ($protocol) {
+		/^80211a$/ && do {
+		    $privcmd .= "1";
+		    last SWITCH1;
+		};
+		/^80211b$/ && do {
+		    $privcmd .= "2";
+		    last SWITCH1;
+		};
+		/^80211g$/ && do {
+		    $privcmd .= "3";
+		    last SWITCH1;
+		};
+	    }
+	}
+	else {
+	    warn("*** WARNING: Unsupported interface type $iface_type!\n");
+	    return undef;
+	}
+	 
+	#
+	# At the moment, we expect just the various flavors of 80211, and
+	# we treat them all the same, configuring with iwconfig and iwpriv.
+	#
+	my $iwcmd = "/sbin/iwconfig $iface ";
+
+	#
+	# We demand to be given an ssid.
+	#
+	if (!exists($settings->{"ssid"})) {
+	    warn("*** WARNING: No SSID provided for $iface!\n");
+	    return undef;
+	}
+	$iwcmd .= "essid ". $settings->{"ssid"};
+
+	# If we do not get a channel, pick one.
+	if (exists($settings->{"channel"})) {
+	    $iwcmd .= " channel " . $settings->{"channel"};
+	}
+	else {
+	    $iwcmd .= " channel 3";
+	}
+
+	# txpower and rate default to auto if not specified.
+	if (exists($settings->{"rate"})) {
+	    $iwcmd .= " rate " . $settings->{"rate"};
+	}
+	else {
+	    $iwcmd .= " rate auto";
+	}
+	if (exists($settings->{"txpower"})) {
+	    $iwcmd .= " txpower " . $settings->{"txpower"};
+	}
+	else {
+	    $iwcmd .= " txpower auto";
+	}
+	# Allow this too. 
+	if (exists($settings->{"sensitivity"})) {
+	    $iwcmd .= " sens " . $settings->{"sensitivity"};
+	}
+
+	#
+	# We demand to be told if we are the master or a peon.
+	# This needs to be last for some reason.
+	#
+	if (!exists($settings->{"accesspoint"})) {
+	    warn("*** WARNING: No accesspoint provided for $iface!\n");
+	    return undef;
+	}
+	my $accesspoint = $settings->{"accesspoint"};
+	my $accesspointwdots;
+
+	# Allow either dotted or undotted notation!
+	if ($accesspoint =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
+	    $accesspointwdots = "$1:$2:$3:$4:$5:$6";
+	}
+	elsif ($accesspoint =~
+	       /^(\w{2}):(\w{2}):(\w{2}):(\w{2}):(\w{2}):(\w{2})$/) {
+	    $accesspointwdots = $accesspoint;
+	    $accesspoint      = "${1}${2}${3}${4}${5}${6}";
+	}
+	else {
+	    warn("*** WARNING: Improper format for MAC ($accesspoint) ".
+		 "provided for $iface!\n");
+	    return undef;
+	}
+	    
+	if (libsetup::findiface($accesspoint) eq $iface) {
+	    $iwcmd .= " mode Master";
+	}
+	else {
+	    $iwcmd .= " mode Managed ap $accesspointwdots";
+	}
+
+	$uplines   = sprintf($IFCONFIG, $iface, $inet, $mask) . "\n";
+	$uplines  .= $privcmd . "\n";
+	$uplines  .= $iwcmd;
+	$downlines = "$IFCONFIGBIN $iface down";
+	return ($uplines, $downlines);
+    }
+
+    #
+    # Need to check units on the speed. Just in case.
+    #
+    if ($speed =~ /(\d*)([A-Za-z]*)/) {
+	if ($2 eq "Mbps") {
+	    $speed = $1;
+	}
+	elsif ($2 eq "Kbps") {
+	    $speed = $1 / 1000;
+	}
+	else {
+	    warn("*** Bad speed units $2 in ifconfig, default to 100Mbps\n");
+	    $speed = 100;
+	}
+	if ($speed == 1000) {
+	    $media = $IFC_1000MBS;
+	}
+	elsif ($speed == 100) {
+	    $media = $IFC_100MBS;
+	}
+	elsif ($speed == 10) {
+	    $media = $IFC_10MBS;
+	}
+	else {
+	    warn("*** Bad Speed $speed in ifconfig, default to 100Mbps\n");
+	    $speed = 100;
+	    $media = $IFC_100MBS;
+	}
+    }
+    if ($duplex eq "full") {
+	$media = "$media-$IFC_FDUPLEX";
+    }
+    elsif ($duplex eq "half") {
+	$media = "$media-$IFC_HDUPLEX";
+    }
+    else {
+	warn("*** Bad duplex $duplex in ifconfig, default to full\n");
+	$duplex = "full";
+	$media = "$media-$IFC_FDUPLEX";
+    }
+
+    #
+    # Linux is apparently changing from mii-tool to ethtool but some drivers
+    # don't support the new interface (3c59x), some don't support the old
+    # interface (e1000), and some (eepro100) support the new interface just
+    # enough that they can report success but not actually do anything. Sweet!
+    #
+    my $ethtool;
+    if (-e "/sbin/ethtool") {
+	$ethtool = "/sbin/ethtool";
+    } elsif (-e "/usr/sbin/ethtool") {
+	$ethtool = "/usr/sbin/ethtool";
+    }
+    if (defined($ethtool)) {
+	# this seems to work for returning an error on eepro100
+	$uplines = 
+	    "if $ethtool $iface >/dev/null 2>&1; then\n    " .
+	    "  $ethtool -s $iface autoneg off speed $speed duplex $duplex\n    " .
+	    "else\n    " .
+	    "  /sbin/mii-tool --force=$media $iface\n    " .
+	    "fi\n    ";
+    } else {
+	$uplines = "/sbin/mii-tool --force=$media $iface\n    ";
+    }
+
+    if ($inet ne "") {
+	$uplines  .= sprintf($IFCONFIG, $iface, $inet, $mask);
+	$downlines = "$IFCONFIGBIN $iface down";
+    }
+    
+    return ($uplines, $downlines);
+}
+
+#
+# Specialized function for configing locally hacked veth devices.
+#
+sub os_ifconfig_veth($$$$$;$$$)
+{
+    return "";
+}
+
+#
+# Generate and return an string that is approriate for putting
+# into /etc/hosts.
+#
+sub os_etchosts_line($$$)
+{
+    my ($name, $ip, $aliases) = @_;
+    
+    return sprintf("%s\t%s %s", $ip, $name, $aliases);
+}
+
+#
+# Add a new group
+# 
+sub os_groupadd($$)
+{
+    my($group, $gid) = @_;
+
+    return system("$GROUPADD -g $gid $group");
+}
+
+#
+# Delete an old group
+# 
+sub os_groupdel($)
+{
+    my($group) = @_;
+
+    return system("$GROUPDEL $group");
+}
+
+#
+# Remove a user account.
+# 
+sub os_userdel($)
+{
+    my($login) = @_;
+
+    return system("$USERDEL $login");
+}
+
+#
+# Modify user group membership.
+# 
+sub os_usermod($$$$$$)
+{
+    my($login, $gid, $glist, $pswd, $root, $shell) = @_;
+
+    if ($root) {
+	$glist = join(',', split(/,/, $glist), "root");
+    }
+    if ($glist ne "") {
+	$glist = "-G $glist";
+    }
+    # Map the shell into a full path.
+    $shell = MapShell($shell);
+
+    return system("$USERMOD -s $shell -g $gid $glist -p '$pswd' $login");
+}
+
+#
+# Add a user.
+# 
+sub os_useradd($$$$$$$$$)
+{
+    my($login, $uid, $gid, $pswd, $glist, $homedir, $gcos, $root, $shell) = @_;
+
+    if ($root) {
+	$glist = join(',', split(/,/, $glist), "root");
+    }
+    if ($glist ne "") {
+	$glist = "-G $glist";
+    }
+    # Map the shell into a full path.
+    $shell = MapShell($shell);
+
+    if (system("$USERADD -M -u $uid -g $gid $glist -p '$pswd' ".
+	       "-d $homedir -s $shell -c \"$gcos\" $login") != 0) {
+	warn "*** WARNING: $USERADD $login error.\n";
+	return -1;
+    }
+    return 0;
+}
+
+#
+# Remove a homedir. Might someday archive and ship back.
+#
+sub os_homedirdel($$)
+{
+    return 0;
+}
+
+#
+# Create a directory including all intermediate directories.
+#
+sub os_mkdir($$)
+{
+    my ($dir, $mode) = @_;
+
+    if (system("$MKDIR -p -m $mode $dir")) {
+	return 0;
+    }
+    return 1;
+}
+
+#
+# OS Dependent configuration. 
+# 
+sub os_setup()
+{
+    return 0;
+}
+    
+#
+# OS dependent, routing-related commands
+#
+sub os_routing_enable_forward()
+{
+    my $cmd;
+
+    $cmd = "sysctl -w net.ipv4.conf.all.forwarding=1";
+    return $cmd;
+}
+
+sub os_routing_enable_gated($)
+{
+    my ($conffile) = @_;
+    my $cmd;
+
+    #
+    # XXX hack to avoid gated dying with TCP/616 already in use.
+    #
+    # Apparently the port is used by something contacting ops's
+    # portmapper (i.e., NFS mounts) and probably only happens when
+    # there are a bazillion NFS mounts (i.e., an experiment in the
+    # testbed project).
+    #
+    $cmd  = "for try in 1 2 3 4 5 6; do\n";
+    $cmd .= "\tif `cat /proc/net/tcp | ".
+	"grep -E -e '[0-9A-Z]{8}:0268 ' >/dev/null`; then\n";
+    $cmd .= "\t\techo 'gated GII port in use, sleeping...';\n";
+    $cmd .= "\t\tsleep 10;\n";
+    $cmd .= "\telse\n";
+    $cmd .= "\t\tbreak;\n";
+    $cmd .= "\tfi\n";
+    $cmd .= "    done\n";
+    $cmd .= "    $GATED -f $conffile";
+    return $cmd;
+}
+
+sub os_routing_add_manual($$$$$;$)
+{
+    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
+    my $cmd;
+
+    if ($routetype eq "host") {
+	$cmd = "$ROUTE add -host $destip gw $gate";
+    } elsif ($routetype eq "net") {
+	$cmd = "$ROUTE add -net $destip netmask $destmask gw $gate";
+    } elsif ($routetype eq "default") {
+	$cmd = "$ROUTE add default gw $gate";
+    } else {
+	warn "*** WARNING: bad routing entry type: $routetype\n";
+	$cmd = "";
+    }
+
+    return $cmd;
+}
+
+sub os_routing_del_manual($$$$$;$)
+{
+    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
+    my $cmd;
+
+    if ($routetype eq "host") {
+	$cmd = "$ROUTE delete -host $destip";
+    } elsif ($routetype eq "net") {
+	$cmd = "$ROUTE delete -net $destip netmask $destmask gw $gate";
+    } elsif ($routetype eq "default") {
+	$cmd = "$ROUTE delete default";
+    } else {
+	warn "*** WARNING: bad routing entry type: $routetype\n";
+	$cmd = "";
+    }
+
+    return $cmd;
+}
+
+# Map a shell name to a full path using /etc/shells
+sub MapShell($)
+{
+   my ($shell) = @_;
+
+   if ($shell eq "") {
+       return $DEFSHELL;
+   }
+
+   my $fullpath = `grep '/${shell}\$' $SHELLS`;
+
+   if ($?) {
+       return $DEFSHELL;
+   }
+
+   # Sanity Check
+   if ($fullpath =~ /^([-\w\/]*)$/) {
+       $fullpath = $1;
+   }
+   else {
+       $fullpath = $DEFSHELL;
+   }
+   return $fullpath;
+}
+
+# Extract the local mount point from a remote mount path.
+sub os_mountlocal($)
+{
+    my ($remote) = @_;
+    my $local = $remote;
+    $local =~ s|^.*:||;			# Remove server prefix.
+    $local =~ s|^/q/proj/|/proj/|;	# Remove /q prefix from /proj.
+    return $local;
+}
+
+sub os_getnfsmounts($)
+{
+    my ($rptr) = @_;
+    my %mounted = ();
+
+    #
+    # Grab the output of the mount command and parse.  ("net use" on Windows.)
+    #
+    if (! open(MOUNT, "$NET use|")) {
+	print "os_getnfsmounts: Cannot run net use command\n";
+	return -1;
+    }
+    while (<MOUNT>) {
+	# We get lines like:
+	#              P:        \\fs.emulab.net\q\proj\testbed       NFS Network
+	# When the remote string is too long, "NFS Network" is on the next line.
+	if ($_ =~ m|^ +(\w:) +([-\w\.\\]+)|) {
+	    # Bash the remote UNC path into a NFS path like fs:/q/proj/testbed .
+	    my $driveletter = $1;
+	    my $rpath = $2;
+	    $rpath =~ s|^\\\\([-\w\.]+)|$1:|; # \\host -> host:
+	    $rpath =~ s|\\|/|g;		      # Backslashes to forward.
+	    # Key is the remote NFS path, value is the CygWin local path.
+	    $mounted{$rpath} = os_mountlocal($rpath);
+	}
+    }
+    close(MOUNT);
+    %$rptr = %mounted;
+    return 0;
+}
+
+sub os_getnfsmountpoints($)
+{
+    my ($rptr) = @_;
+    my %mounted = ();
+
+    #
+    # Grab the output of the mount command and parse.  ("net use" on Windows.)
+    #
+    if (! open(MOUNT, "$NET use|")) {
+	print "os_getnfsmounts: Cannot run net use command\n";
+	return -1;
+    }
+    while (<MOUNT>) {
+	# We get lines like:
+	#              P:        \\fs.emulab.net\q\proj\testbed       NFS Network
+	# When the remote string is too long, "NFS Network" is on the next line.
+	##print;
+	if ($_ =~ m|^ +(\w:) +([-\w\.\\]+)|) {
+	    # Bash the remote UNC path into a NFS path like fs:/q/proj/testbed .
+	    my $driveletter = $1;
+	    my $rpath = $2;
+	    ##print "driveletter '$driveletter', rpath '$rpath'\n";
+	    $rpath =~ s|^\\\\([-\w\.]+)|$1:|; # \\host -> host:
+	    $rpath =~ s|\\|/|g;		      # Backslashes to forward.
+	    ##print "driveletter '$driveletter', rpath '$rpath'\n";
+	    # Key is CygWin local path, value is drive letter and remote NFS path.
+	    my $local = os_mountlocal($rpath);
+	    ##print "local '$local'\n";
+	    $mounted{$local} = [$rpath, $driveletter];
+	}
+    }
+    close(MOUNT);
+    %$rptr = %mounted;
+    return 0;
+}
+
+# Execute a noisy bash command, throwing away the output unless we ask for it.
+sub os_noisycmd($$)
+{
+    my ($cmd, $verbose) = @_;
+    my $bashcmd = "$BASH -c '$cmd'" . ($verbose ? "" : " > /dev/null");
+    my $ret = system($bashcmd);
+    ##print "os_noisycmd cmd '$cmd', ret $ret\n";
+    return $ret
+}
+
+sub os_fwconfig_line($@)
+{
+    my ($fwinfo, @fwrules) = @_;
+    my ($upline, $downline);
+    my $errstr = "*** WARNING: Linux firewall not implemented\n";
+
+
+    warn $errstr;
+    $upline = "echo $errstr; exit 1";
+    $downline = "echo $errstr; exit 1";
+
+    return ($upline, $downline);
+}
+
+sub os_fwrouteconfig_line($$$)
+{
+    my ($orouter, $fwrouter, $routestr) = @_;
+    my ($upline, $downline);
+
+    #
+    # XXX assume the original default route should be used to reach servers.
+    #
+    # For setting up the firewall, this means we create explicit routes for
+    # each host via the original default route.
+    #
+    # For tearing down the firewall, we just remove the explicit routes
+    # and let them fall back on the now re-established original default route.
+    #
+    $upline  = "for vir in $routestr; do\n";
+    $upline .= "        $ROUTE delete \$vir >/dev/null 2>&1\n";
+    $upline .= "        $ROUTE add -host \$vir gw $orouter || {\n";
+    $upline .= "            echo \"Could not establish route for \$vir\"\n";
+    $upline .= "            exit 1\n";
+    $upline .= "        }\n";
+    $upline .= "    done";
+
+    $downline  = "for vir in $routestr; do\n";
+    $downline .= "        $ROUTE delete \$vir >/dev/null 2>&1\n";
+    $downline .= "    done";
+
+    return ($upline, $downline);
+}
+
+1;
diff --git a/tmcd/cygwinxp/profile b/tmcd/cygwinxp/profile
new file mode 100755
index 0000000000..da89fdb4af
--- /dev/null
+++ b/tmcd/cygwinxp/profile
@@ -0,0 +1,77 @@
+# Some resources...
+
+# Customizing Your Shell: http://www.dsl.org/cookbook/cookbook_5.html#SEC69
+# Consistent BackSpace and Delete Configuration:
+#   http://www.ibb.net/~anne/keyboard.html
+
+# Setup some default paths.  Note that this order will allow user installed
+#  software to override 'system' software
+
+# If you wish to change the path on a user by user basis, it is recommended you
+#  edit ~/.bashrc
+
+# If you wish to change the path for all users, it is recommended you edit
+#  /etc/bash.bashrc
+
+export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
+
+# Set the user id
+export USER="`id -un`"
+
+# Here is how HOME is set, in order of priority, when starting from Windows
+# 1) From existing HOME in the Windows environment, translated to a Posix path
+# 2) from /etc/passwd, if there is an entry with a non empty directory field
+# 3) from HOMEDRIVE/HOMEPATH
+# 4) / (root)
+
+# Use a local dir under sshd for now.
+if [ ! -d "$HOME" ]; then
+   HOME=/home/$USER
+fi
+# If the home directory doesn't exist, create it.
+if [ ]; then
+  mkdir -p "$HOME"
+  # copy skeleton files
+  cd /etc/skel
+  for f in `/bin/find . -type f`; do
+    fDest=`echo $f | sed -e 's/^\.//g'`
+    if [ ! -e "$HOME$fDest" -a ! -L "$HOME$fDest" ]; then
+      cp "$f" "$HOME/$fDest"
+    fi
+  done
+fi
+
+# run all of the profile.d scripts
+for i in /etc/profile.d/*.sh ; do
+  if [ -f $i ]; then
+    . $i
+  fi
+done
+
+# default to unix make mode
+export MAKE_MODE=unix
+
+# it is recommended that cvs uses ssh for it's remote shell environment
+export CVS_RSH=/bin/ssh
+
+# Patches to Cygwin always appreciated ;)
+# export CVSROOT=:pserver:anoncvs@sources.redhat.com:/cvs/src
+
+# Set a HOSTNAME variable
+export HOSTNAME=`hostname`
+
+# set a default prompt of: user@host current_directory
+export PS1='\[\033]0;\w\007
+\033[32m\]\u@\h \[\033[33m\w\033[0m\]
+$ '
+
+# uncomment to use the terminal colours set in DIR_COLOR
+# eval `dircolors -b /etc/DIR_COLOR`
+
+# default to removing the write permission for group and other
+# (files normally created with mode 777 become 755; files created with
+# mode 666 become 644)
+umask 022
+
+# make sure we start in home
+cd "$HOME"
diff --git a/tmcd/cygwinxp/rc.cygwinxp b/tmcd/cygwinxp/rc.cygwinxp
new file mode 100755
index 0000000000..b96e477ee8
--- /dev/null
+++ b/tmcd/cygwinxp/rc.cygwinxp
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+# CygWin startup.
+
+# Make sure the node name is right.
+nodeid=`/usr/local/etc/emulab/tmcc.bin nodeid`
+hostname=`/bin/hostname`
+if [ $nodeid == $hostname ]; then
+    echo "Host name '$hostname' matches nodeid '$nodeid'." > /tmp/wsname
+else
+    echo "Host name '$hostname' and nodeid '$nodeid' differ." > /tmp/wsname
+
+    # Change hostname and computername, rename My Computer, reboot on success.
+    /usr/local/etc/emulab/WSName /N:$nodeid /REBOOT /MCN
+fi
diff --git a/tmcd/cygwinxp/rc.cygwinxp-user b/tmcd/cygwinxp/rc.cygwinxp-user
new file mode 100644
index 0000000000..66508738c7
--- /dev/null
+++ b/tmcd/cygwinxp/rc.cygwinxp-user
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+# CygWin user setup for each login.
+
+PATH=/usr/local/etc/emulab:$PATH
+set -x
+
+# Mounts are local to the Win32 login session context.
+if [ -e /cygdrive/s ]; then /cygdrive/c/SFU/common/umount S: > /dev/null; fi
+emount fs.emulab.net:/share /share
+
+if [ -e /cygdrive/p ]; then /cygdrive/c/SFU/common/umount P: > /dev/null ; fi
+proj=`tmcc.bin status | sed 's|ALLOCATED=\([^/]*\)/.*|\1|'`
+emount fs.emulab.net:/q/proj/$proj /proj/$proj
+
+if [ -e /cygdrive/h ]; then /cygdrive/c/SFU/common/umount H: > /dev/null ; fi
+creator=`tmcc.bin creator | sed 's|.*CREATOR=\([^ ]*\).*|\1|'`
+emount fs.emulab.net:/users/$creator /users/$creator
+
+if [ -e /cygdrive/i ]; then /cygdrive/c/SFU/common/umount I: > /dev/null ; fi
+swapper=`tmcc.bin creator | sed 's|.*SWAPPER=\([^ ]*\).*|\1|'`
+if [ $swapper != $creator ]; then
+    emount fs.emulab.net:/users/$swapper /users/$swapper
+fi
+
+# Make sure the user's homedir is mounted if neither creator nor swapper.
+if [ -e /cygdrive/j ]; then /cygdrive/c/SFU/common/umount J: > /dev/null ; fi
+user=`id -un`
+if [[ $user != $creator && $user != $swapper && $user != root ]]; then
+    emount -d J: fs.emulab.net:/users/$user /users/$user
+fi
+
+set +x
+echo "Hit <Enter> to dismiss."
+sed 1q
+
-- 
GitLab