OpenSolaris_b135/pkgdefs/common_files/i.hosts

#!/bin/sh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#

merge_ipnodes() {

	/usr/bin/nawk '
	function getaddress (lineindex,  entryfields) {
		split(lines[lineindex, LINE], entryfields);
		return entryfields[1];
	}

	#
	# Return a string comprised of space delimited names.  This function
	# handles continuation lines.
	#
	function getnames (lineindex,  i, morenames, names, nameindex, n, comment) {
		i = lineindex;
		morenames = 0;
		names = "";
		do {
			# We only want to look at the text before any trailing comment
			comment = (split(lines[i, LINE], linebreakdown, "#") == 2);
			#
			# Now split the stuff before the comment into individual names
			# Note that the names begin at the second entry, except
			# for continuation lines, for which the names begin at the
			# first entry:
			# <addr> <name> [<name> ...] \
			#     <name> [<name> ...]
			#
			n = split(linebreakdown[1], namesarray);
			for (nameindex = morenames ? 1 : 2; \
			    nameindex <= n && namesarray[nameindex] != "\\"; \
			    nameindex++) {
				names = names namesarray[nameindex] " ";
			}

			#
			# Check for a continuation line with more entries only if
			# the line didnt end in a comment.
			#
			morenames = 0;
			if (!comment && match(lines[i, LINE], /\\$/)) {
				i++;
				morenames = 1;
			}
		} while (morenames);

		return names;
	}

	# delete a line and any potential continuation of that line
	function deleteline (deleteindex,  i, n, num, 
			namestr, names, name) {

		lines[deleteindex, TYPE] = DELETED_LINE;
		# delete the continuation lines if present
		for (i = deleteindex + 1; lines[i, TYPE] == CONTINUATION_LINE; i++)
			lines[i, TYPE] = DELETED_LINE;
		#
		# If this entry was preceded by comment lines, delete
		# the comments.  We assume the comments were only meaningful in the
		# context of the entry we just deleted.
		#
		for (i = deleteindex - 1; i>firstentryline && lines[i, TYPE] != ENTRY_LINE; i--) {
			if (lines[i, TYPE] == COMMENT_LINE)
				lines[i, TYPE] = DELETED_LINE;
			else
				break;
		}

		# Update the name cache
		namestr = getnames(deleteindex);
		if (namestr == "")
			return;
		num = split(namestr, names);
	        for (i=1; i<=num; i++) {
			name = names[i];
			for (n=1; n<=namecache[name,0]; n++) {
				if ( namecache[name,n] == deleteindex)
					namecache[name,n] = "";
			}
		}	
	}

	#
	# Count the number of lines that the line at lineindex spans.  For example,
	# a line with one continuation line spans two lines.
	#
	function linespan (lineindex,  i) {
		for (i = 1; lines[lineindex + i, TYPE] == CONTINUATION_LINE; i++);
		return i;
	}

	#
	# Take the line at "fromindex" and insert it at "toindex", bumping down
	# anything currently at and after "toindex" to make the line fit.
	#
	function insertline (fromindex, toindex,  i, linecount,
		       moveindex, namestr, names, name, m, num) {
		if (toindex >= fromindex) {
			print "ERROR, moving a line forward from index " fromindex \
			    " to " toindex;
			exit 1;
		}

		#
		# We do not re-arrange the array to move the lines around as it is expensive.
		# Instead we mark the moved line(s) as deleted and maintain 
		# a array list of the appended line(s) in the target line.
		# MOVEDLINES is the index where we maitain refs/linenos.
		#
		deleteline(fromindex); 	# mark moved lines as deleted.

		#
		# If the target line at "toindex" spans several lines
		# then maintain the list in the last line.
		#
		toindex = toindex + linespan(toindex) - 2; 
		moveindex = lines[toindex, MOVEDLINES, 0]
		linecount = linespan(fromindex);
		for (i = 0; i < linecount; i++) {
			moveindex++;
			lines[toindex, MOVEDLINES, moveindex] = fromindex + i;
		}
		lines[toindex, MOVEDLINES, 0] = moveindex;	

		# Update the name cache
		namestr = getnames(fromindex);
		if (namestre == "")
			return;
		num = split(namestr, names);
	        for (i=1; i<=num; i++) {
			name = names[i];
			for(m=1; m<= namecache[name,0]; m++) {
				if (namecache[name,m] == fromindex)
					namecache[name,m] = toindex;
			}
		}		
	}

	# lookup multiple lines with same host name using namecache
	function findname (name, curline, ln, lncount, matchline) {
		lncount = namecache[name, 0];
		if (lncount < 2)
			return 0;
		for (ln = 1; ln <= lncount; ln++) {
			matchline = namecache[name, ln];
			if (matchline == "")
				continue;
			if (matchline >= curline)
				continue;
			if (lines[matchline, TYPE] != ENTRY_LINE)
				continue;
			return matchline;
		}
		return 0;
	}

	function arelinesadjacent(a, b,  i) {
		if (a + 1 == b)
			return 1;
		for (i = a + 1; i < b; i++) {
			if (lines[i, TYPE] == ENTRY_LINE) 
				return 0;
		}
		return 1;
	}

	function printmovedlines(movedline, mcount, mline, m) {
		mcount =  lines[movedline, MOVEDLINES, 0];
		if (mcount > 0) {
			for (m = 1; m <= mcount; m++) {
				mline = lines[movedline, MOVEDLINES, m];
				print lines[mline, LINE];
				printmovedlines(mline);
			}
		}
	}

	function printmergeinfo() {
		"/usr/bin/date" | getline date;
		printf "\n#\n# Merged entries from ipnodes" \
			" into hosts on <%s>\n",date;
		printf "# Backup files saved in /etc/inet/ directory:" \
		       " hosts.premerge, ipnodes.premerge\n#\n";
	}

	function printhostsfile (i) {
		for (i = 1; i <= linecount; i++) {
			# Add comment about occurrence of merge.
			if (!cadded && (lines[i, TYPE] != COMMENT_LINE)) {
					printmergeinfo();
					cadded = 1;
			}

			if (lines[i, TYPE] != DELETED_LINE)
				print lines[i, LINE];
			#
			# Check for moved lines and print them as they should be
		        # appended after this line.	
			#
			printmovedlines(i);
		}
	}

	BEGIN {
		# line types.  These are strings to help with debugging
		ENTRY_LINE = "entry";
		COMMENT_LINE = "comment";
		BLANK_LINE = "blankline";
		DELETED_LINE = "deleted";
		CONTINUATION_LINE = "continuation";

		# indices to the data contained in the lines array
		TYPE = 1;
		LINE = 2;
		MOVEDLINES = 3;  # index to array of append. line nos

		# regular expressions
		space = "[ \t]";
		blanks = space "*";
		blankline = "^" blanks "$";
		comment = "^" blanks "#";

		linecount = 0;
		tobecontinued = 0;
	}

	$0 ~ comment {
		linecount++;
		if (tobecontinued) {
			lines[linecount, TYPE] = CONTINUATION_LINE;
			tobecontinued = 0;
		} else {
			lines[linecount, TYPE] = COMMENT_LINE;
		}
		lines[linecount, LINE] = $0;
		next;
	}

	$0 ~ blankline {
		linecount++;
		if (tobecontinued) {
			lines[linecount, TYPE] = CONTINUATION_LINE;
			tobecontinued = 0;
		} else {
			lines[linecount, TYPE] = BLANK_LINE;
		}
		next;
	}

	{
		linecount++;
		if (firstentryline == "")
			firstentryline = linecount;

		if (tobecontinued) {
			lines[linecount, TYPE] = CONTINUATION_LINE;
			tobecontinued = 0;
		} else {
			lines[linecount, TYPE] = ENTRY_LINE;
		}
		lines[linecount, LINE] = $0;
	}

	/\\$/ {
		#
		# This matches a line that is continued on a subsequent line.  It
		# doesnt match the continuation itself.  We only need to flag that
		# this line is continued so that subsequent records can be tagged
		# as continuations.
		#
		tobecontinued = 1;
	}

	#
	# We now have an array of lines, one for each line of input.
	#
	END {
		#
		# Start by removing duplicate lines. We look for two lines 
		# that are for the same address and have the same hostname 
		# information (and listed in the same order). If we find such 
		# a pair, we delete the second line.
		#
		for (i = 1; i <= linecount; i++) {
			if (lines[i, TYPE] != ENTRY_LINE)
				continue;

			# Extract the address from the line
			address = getaddress(i);

			# Extract the hostnames from the line
			namestr = getnames(i);
			if (namestr == "")
				continue;
			#
			# We keep an array of address indices.  This lets us know
			# when we have duplicates without having to go back and
			# search the lines array. Since there can be multiple host 
			# entries of the same address it is a two-dimensional array.
			#
			if (addrindex[address] == "") {
				# We have not seen this address before
				addrindex[address] = 1;
				# The second dimension stores the line number.
				addrindex[address, 1] = i;
			} else {
				#
				# We have a duplicate if hostname
				# information is identical. If duplicate 
				# just mark the second entry as deleted.
				#
				for (l = 1; l <= addrindex[address]; l++) {
					if (namestr == \
				           getnames(addrindex[address, l])) {
						deleteline(i);
						break;
					}
				}
				
				#
				# If it was not deleted as a duplicate, add this
				# address to the index array.
				#
				if (l > addrindex[address]) {
					addrindex[address]++;
					addrindex[address, addrindex[address]] = i;
				}
			}

			# Maintain a name cache array. This is used
			# next to coalesce entries with same host
			# name. Line deletions and insert lines do
			# update the name cache.

			num = split(namestr, names);
			for (n = 1; n <= num; n++) {
				# 0 index node holds count of lines
				# with same host name
				namecache[names[n],0]++;
				namecache[names[n], 
					namecache[names[n],0]] = i;
			}
		}
		
		#
		# We now need to bring together entries that contain the same name.
		# The hosts and ipnodes back-end requires that entries for the same
		# name but for different addresses be on adjacent lines.  See
		# hosts(4).
		#
		for (i = 1; i <= linecount; i++) {
			if (lines[i, TYPE] != ENTRY_LINE)
				continue;

			namestr = getnames(i);
			if (namestr == "")
				continue;
			num = split(namestr, names);
			for (n = 1; n <= num; n++) {
				if ((nameindex = findname(names[n], i )) != 0) {
					if (!arelinesadjacent(nameindex, i)) {
						for (newindex = nameindex + 1; \
						     lines[newindex, TYPE] == \
							 CONTINUATION_LINE; \
						     newindex++);
						insertline(i, newindex);
					}
					break;
				}
			}
		}

		# Remove any trailing blank lines
		for (i = linecount; i > 0; i--) {
			type = lines[i, TYPE];
			if (type == ENTRY_LINE || type == COMMENT_LINE)
				break;
			if (type == DELETED_LINE)
				continue;
			if (type == BLANK_LINE)
				deleteline(i);
		}

		printhostsfile();
	}' $1 $2;

}

#
# deliver_hosts: This function merges /etc/inet/hosts and /etc/inet/ipnodes
# into a single hosts file (/etc/inet/hosts) only when ipnodes file exists in
# the system.  /etc/inet/ipnodes is now a symlink to /etc/inet/hosts.
#
deliver_hosts() {
	saved_ipnodes_file=$BASEDIR/etc/inet/ipnodes.hostsmerge
	temp_ipnodes_file=/tmp/ipnodes.hostsmerge
	temp_merged_file=/tmp/hosts.hostsmerged
	
	# if /etc/inet/hosts doesn't exist (fresh install)
	# then same action as 'i.preserve' i.e
	# copy default /etc/inet/hosts in place.

	if [ ! -f $dest ] ; then
		cp $src $dest
	fi

	if [ -f $saved_ipnodes_file ] ; then

		# Save copies before merge
		cp -pf $dest $BASEDIR/etc/inet/hosts.premerge
		cp -pf $saved_ipnodes_file $BASEDIR/etc/inet/ipnodes.premerge 

		# Remove redundant header lines from ipnodes file 
		/usr/bin/sed -e '
		/^# CDDL HEADER START$/,/^# CDDL HEADER END$/ d
		/^# Copyright .* Sun Microsystems, Inc/ d
		/^# Use is subject to license terms/ d
		/^# Internet host table$/ d
		' $saved_ipnodes_file | /usr/bin/sed -e ' /^#$/ {
		# Remove blank comment line pairs
			$!N
			/\n#$/ d
		}' > $temp_ipnodes_file

		merge_ipnodes $dest $temp_ipnodes_file \
			> $temp_merged_file
		if [ $? -ne 0 ] ; then
			echo "$0 : failed to merge \
			       	$saved_ipnodes_file with $dest"
			exit_status=2
			continue
		fi

		mv -f $temp_merged_file $dest
		if [ $? -ne 0 ] ; then
			echo "$0 : failed to move \
				$temp_merged_file to $dest"
			exit_status=2
			continue
		fi
	fi

	# Set correct permissions on hosts file
	chmod 0644 $dest
}
	
# main
exit_status=0

while read src dest; do
	dest_name=`basename "$dest"`

	case "${dest_name}" in 
		"hosts") deliver_hosts ;;
		*) ;;
	esac
done

exit $exit_status