#!/usr/bin/perl

# Written by Tim Ellis, loosely based off of a script by Dale Johnson
#
# Copyright(c)2005 by Tim Ellis
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation version 2 of the License.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the
#
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor,
# Boston, MA  02110-1301, USA.
#
# ----------------------------------------------------------------------------
#
# This is a simple daemonised program to do a "show full processlist" very
# quickly, logging all SQL statements into a file, and all statistics for the
# same into another. It is basically a daemonised version of "mytop."
#
# ----------------------------------------------------------------------------
# 
# Do these grants for this script on your DB:
#
#     grant super,process on *.* to dbmon@'127.0.0.1' identified by 'dbmon'
#
# Of course choose the actual password, not 'dbmon' then change $dbPass below

use strict;
use warnings;
use DBI;
use Digest::MD5 qw/md5_hex/;
use POSIX qw/setsid/;
use POSIX qw/strftime/;
use Time::HiRes qw/gettimeofday usleep/;

# database login information
my $dbUser = "dbmon";
my $dbPass = "dbmon";
my $dbMachine = "127.0.0.1:3306";

# how many milliseconds to sleep in our "show full processlist" loop
#   ms    # per second
#   ---   ------------
#   500 = 2/sec
#   200 = 5/sec
#   100 = 10/sec
#   50  = 20/sec
#   25  = 40/sec
#   10  = 100/sec
# This is only an estimate, because the algorithm is a simple sleep for
# X milliseconds between each run. If the run takes 100 milliseconds, the
# actual run interval will be X+100 milliseconds.
my $interval = 25 * 1000;

my $daemon;
my $killLongRunning = 0;
my $noKillVolatile = 1;
my $queryBytes = 1024;
my $killConn = 0;
my $doRotate = 0;
my $logDir;
# process input arguments -- mainly, are we a daemon or what?
if (scalar @ARGV < 1) { doArgs(); exit 0; }
foreach my $arg (@ARGV) {
	if ($arg =~ /^-+h(elp)*$/)        { doArgs(); exit 0; }
	if ($arg =~ /^-+querybytes=(\d+)$/) { $queryBytes = $1; }
	if ($arg =~ /^-+daemon$/)         { $daemon = 1; }
	if ($arg =~ /^-+terminal$/)       { $daemon = 0; }
	if ($arg =~ /^-+license$/)        { doLicense(); exit 0; }
	if ($arg =~ /^-+killlong=(\d+)$/) { $killLongRunning = $1; }
	if ($arg =~ /^-+host=(.*)$/)      { $dbMachine = $1; }
	if ($arg =~ /^-+killvolatile$/)   { $noKillVolatile = 0; }
	if ($arg =~ /^-+killconn$/)       { $killConn = 1; }
	if ($arg =~ /^-+rotate$/)         { $doRotate = 1; }
	if ($arg =~ /^-+logdir=(.*)$/)    { $logDir = $1; }
}

print STDERR " - Host is $dbMachine\n";

# logging locations
unless(defined $logDir) { $logDir = "/var/log/dbmon"; }
my $logFile = "dbmon-$dbMachine.log";
my $errFile = "dbmon-$dbMachine.err";
my $sqlFile = "dbmon-$dbMachine.sql";

# we'll overwrite this from time to time
my $currTime = strftime ('%Y-%m-%d %H:%M:%S', localtime);

# kill existing monitor
print " - Looking for existing monitor...\n";
my $killCount = 0;
my $pid = `ps auxww | grep "MySQL Query Monitor" | grep $logFile | grep -v grep | head -1 | awk '{print \$2;}'`;
while ($pid && $killCount++ <= 5) {
	print " + Killing existing monitor...\n";
	`kill -2 $pid`;
	usleep 200000;
	$pid = `ps auxww | grep "MySQL Query Monitor" | grep $logFile | grep -v grep | head -1 | awk '{print \$2;}'`;
	if ($pid) { `kill -9 $pid`; }
	usleep 50000;
	$pid = `ps auxww | grep "MySQL Query Monitor" | grep $logFile | grep -v grep | head -1 | awk '{print \$2;}'`;
}
if ($killCount > 4) {
	print " - Had trouble killing old monitor. Check logs at $logDir\n";
	exit (255);
}

# rotate out old log info if user requests it
if ($doRotate) {
	my $fileText;
	print " - Rotating out log information\n";
		doRotate ("$errFile");
		doRotate ("$sqlFile");
		doRotate ("$logFile");
}

# make the logdir if it doesn't exist
mkdir "$logDir" unless (-d "$logDir");

# open various log files
close (STDIN);
close (STDOUT);
close (STDERR);
open (STDIN, "</dev/null");
open (STDERR, ">>$logDir/$errFile");
open (STDOUT, ">>$logDir/$logFile");
open (SQLLOGFILE, ">>$logDir/$sqlFile");

# set unbuffered output. it is not an accident that
# i choose to select STDOUT last.
select SQLLOGFILE; $| = 1;
select STDERR; $| = 1;
select STDOUT; $| = 1;

print STDOUT     "\n";
print STDOUT     " - Invocation args: @ARGV\n";
print STDOUT     " - Statistics Messages will go into $logDir/$logFile at $currTime\n";
print STDOUT     " - MD5 hash-->SQL mapping is in $logDir/$sqlFile\n";
print STDERR     "\n";
print STDERR     "  - Error Messages will go into $logDir/$errFile at $currTime\n";
print SQLLOGFILE "\nmd5-hash\tquery-text\n";

# daemonise!!!
if ($daemon) {
	print " - Daemonising...\n";

	exit (0) if (fork());
	POSIX::setsid();
	exit (0) if (fork());
	$0 = "$0 killLongRunning=$killLongRunning MySQL Query Monitor - see $logDir/$logFile ; @ARGV";
}

# make database connection
my $dbh;
my $sth;

&connectDb();

my %queryDetails;
my %connectionDetails;
my $digest;
my $queryToPrint;

while (1) {
	$sth->execute();
	if ($DBI::errstr) {
		warn "Database error: $DBI::errstr\n";

		# make sure we're connected
		$dbh->disconnect();
		&connectDb();
	}

	my @this_dbid = ();

	# loop through all processes
	while (my @row = $sth->fetchrow()) {
		$currTime = strftime ('%Y-%m-%d %H:%M:%S', localtime);

		my ($dbid, $user, $host, $dbname, $state, $time, $action, $query) = map { defined $_ ? $_ : "undef" } @row;

		# don't pay any attention to these administrative queries
		next if ($user eq "System User" || $user eq "nodbmonuser");
		next if ($state eq "Sleep" || $state eq "Connect");
		next if ($query eq "undef" || $query eq "show full processlist");

		# make the query easier to view in logs
		$query =~ s/[\t\n\r]/ /g;

		# if, in comments, there's a [queryName=Name] or [queryID=id] then use
		# that for the hash, not the full query
		if ($query =~ /\[queryName=(.+?)\]/ || $query =~ /\[queryID=(.+?)\]/) {
			$digest = md5_hex($1);
		} else {
			# attempt to strip out constants that are variable per query before
			# hashing it
			my $stripQuery = $query;
			$stripQuery =~ s/'.+?'/STR/g;
			$stripQuery =~ s/\s+[\d.\-]+\s+/ NUM /g;
			$stripQuery =~ s/wbo\d+/wboNN/g;
			$digest = md5_hex($stripQuery);
		}

		# i think just the dbid is enough to uniquely identify the query for
		# future identification and killing
		my $detailsKey = $dbid;

		# store the list of currently-running queries
		push @this_dbid, $query;

		# logic based on a query we know about
		if (exists $queryDetails{$detailsKey}) {
			# change in state of query
			if ($state ne $queryDetails{$detailsKey}{'state'}) {
				printf STDOUT $queryDetails{$detailsKey}{'digest'} 
				. "\tState\t$currTime\t$user\@$host\t$state\tid=" 
				. $queryDetails{$detailsKey}{'dbid'} . "\t"
				. "%3.3f sec\n", gettimeofday() - $queryDetails{$detailsKey}{'time'};
				$queryDetails{$detailsKey}{'state'} = $state;
			}

			# note how long it's been running for future killing
			# A state of '' means it's doing nothing currently.
			if ($state ne '') {
				$queryDetails{$detailsKey}{'uSeconds'} += $interval; 
			}

			# kill long-running queries but only attempt this every 5 seconds
			# TODO: should only kill a query once per N seconds but the logic
			# must be horked. get many kills for one query.
			my $killFlag = 0;
			my $connTime = 0;
			if (exists $connectionDetails{$detailsKey}) { $connTime = gettimeofday() - $connectionDetails{$detailsKey}; }

			if (
				($killLongRunning && !$killConn && ($time >= $killLongRunning)) 
				||
				($killLongRunning &&  $killConn && ($connTime >= $killLongRunning))
			   ) {
				# only kill queries with case-insensitive "select "
				# at the beginning, discounting /*comment*/ stuff
				# in them if we are only killing nonvolatile queries
				if ($noKillVolatile && $query !~ /^(\/\*.+\*\/)*\s*select /i) {
					if (!$queryDetails{$detailsKey}{'tokill'}) {
						print STDERR " - $currTime :: Not killing volatile query. digest=$digest query=$query\n";
						$queryDetails{$detailsKey}{'tokill'} = int(12500000 / $interval);
					}
				} elsif ($query =~ /allowtorun=(\d+)/) {
					my $secondsAllowed = $1;
					if ($time > $secondsAllowed) {
						$killFlag = 1;
					}
				} else {
					$killFlag = 1;
				}

				# if the query won't die, we decrement tokill until it hits zero
				# again, at which point the query becomes valid to kill again
				if ($queryDetails{$detailsKey}{'tokill'} > 0) {
					$queryDetails{$detailsKey}{'tokill'} -= 1;
				}
			   
				if ($killFlag && !$queryDetails{$detailsKey}{'tokill'}) {
					my $runTime = sprintf ('%.2fsec',$queryDetails{$detailsKey}{'uSeconds'} / 1000000);
					print STDERR " - $currTime :: Attempting to kill digest=$digest runtime=$runTime query=$query\n";
					$dbh->do ("kill $dbid");
					print STDOUT "$digest\tAutoKll\t$currTime\t$user\@$host\t$state\tid=$dbid\n";
					$queryDetails{$detailsKey}{'tokill'} = int(12500000 / $interval);
				}
			}
		} else {
			$queryDetails{$detailsKey}{'query'}  = $query;
			$queryDetails{$detailsKey}{'time'}   = gettimeofday();
			$queryDetails{$detailsKey}{'state'}  = $state;
			$queryDetails{$detailsKey}{'user'}   = $user;
			$queryDetails{$detailsKey}{'host'}   = $host;
			$queryDetails{$detailsKey}{'dbid'}   = $dbid;
			$queryDetails{$detailsKey}{'digest'} = $digest;
			$queryDetails{$detailsKey}{'tokill'} = 0; 
			$queryDetails{$detailsKey}{'uSeconds'} = 0; 

			# get the info about the connection
			unless ($connectionDetails{$detailsKey}) { $connectionDetails{$detailsKey} = gettimeofday(); }
			## we need to figure out where we can do this - can't do it at query
			## end, because the connection doesn't end there. :( - need to do something
			## like the grep below on the donequery bit, but for all connections, not
			## just connections doing something.
			#delete $connectionDetails{$queryKey};

			print STDOUT "$digest\tStart\t$currTime\t$user\@$host\t$state\tid=$dbid\n";
			if (length($query) > $queryBytes) {
				$queryToPrint = substr($query,0,$queryBytes/2) . "...[snip]..." . substr($query,0 - $queryBytes/2);
			} else {
				$queryToPrint = $query;
			}
			print SQLLOGFILE "$digest\t$queryToPrint\n";
		}

	}

	# look through queries checking for done ones
	for my $queryKey (keys %queryDetails) {
		$digest = $queryDetails{$queryKey}{'digest'};
		my $user = $queryDetails{$queryKey}{'user'};
		my $host = $queryDetails{$queryKey}{'host'};
		my $state = $queryDetails{$queryKey}{'state'};

		# this is a done query -- write out stats about it
		if (! scalar grep ( { $queryDetails{$queryKey}{'query'} eq $_ } @this_dbid)) {
			$currTime = strftime ('%Y-%m-%d %H:%M:%S', localtime);
			printf STDOUT "$digest\tFinish\t$currTime\t$user\@$host\t$state\tid="
			. $queryDetails{$queryKey}{'dbid'} . "\t"
			. "%3.3f sec\n", gettimeofday() - $queryDetails{$queryKey}{'time'};
			delete $queryDetails{$queryKey};
		}
	}
	usleep $interval;
}


sub doRotate {
	my $fileName = shift;
	my $moveTo;
	my $idx;

	print "   - $fileName\n";
	if (-e "$logDir/$fileName") {
		foreach $idx (5,4,3,2,1,0) {
			if (-e "$logDir/$fileName.$idx") {
				$moveTo = $idx + 1;
				if (-e "$logDir/$fileName.$moveTo") { unlink "$logDir/$fileName.$moveTo"; }
				rename ("$logDir/$fileName.$idx", "$logDir/$fileName.$moveTo");
			}
		}
		rename ("$logDir/$fileName", "$logDir/$fileName.0");
	}
}


sub connectDb {
	print STDERR " - $currTime :: Attempting to connect to database $dbMachine\n";
	$dbh = DBI->connect("dbi:mysql:information_schema:$dbMachine", "$dbUser", "$dbPass", { RaiseError => 0});
	# we're going to run this command over and over millions of times
	eval { $sth = $dbh->prepare("show full processlist"); };

	while ($@) {
		# give the database a little breathing room whilst we try to reconnect
		# forever
		print STDERR " - $currTime :: Sleeping 5 seconds before attempting another reconnect\n";
		sleep 5;

		$dbh = DBI->connect("dbi:mysql:information_schema:$dbMachine", "$dbUser", "$dbPass", { RaiseError => 0});
		# we're going to run this command over and over millions of times
		eval { $sth = $dbh->prepare("show full processlist"); };
	}
}


sub doArgs {
	print "\n";
	print "usage: $0 {--daemon|--terminal} [--help] [--rotate]\n";
	print "    [--killlong=N] [--killvolatile] [--killconn] [--host=host[:port]] [--license]\n";
	print "Required argument:\n";
	print "  --daemon       run as a daemon, logging into $logDir\n";
	print "  --querybytes=N log at most N bytes of a given query, default=1024\n";
	print "  --terminal     run in terminal, STDOUT is statistics, STDERR is queries\n";
	print "Optional arguments:\n";
	print "  --host=h[:p]   connect to a specific host and (optional) :port\n";
	print "  --killlong=N   kill >N-second-running things ('thing'==query by default, but can be connection)\n";
	print "  --killvolatile also kill volatile (insert/update/delete) long-running queries\n";
	print "  --killconn     kill long-connected connections, not long-running queries\n";
	print "  --rotate       do stupid (but effective) rotation of logs in $logDir\n";
	print "  --license      how is this program licensed?\n";
	print "  --help         get help\n";
	print "\n";
	print "By default, when killing long-running queries, only SELECTs will be killed.\n";
	print "It can be dangerous to kill volatile queries, since many apps don't expect\n";
	print "that to happen.  If the query has 'allowtorun=N' (N is a number) in a comment,\n";
	print "it can run N seconds before being killed.  When run in --daemon mode, will log\n";
	print "output to logfiles.  Run with --rotate periodically if you don't use\n";
	print "log rotation software on the log files.\n";
}


sub doLicense {
	my $licenseText;

	$licenseText = "\n"
	. qq{ Copyright(c)2005 by Tim Ellis, all rights reserved except as laid out by the \n }
	. qq{ license below. \n }
	. qq{  \n }
	. qq{ GNU GENERAL PUBLIC LICENSE \n }
	. qq{ Version 2, June 1991 \n }
	. qq{  \n }
	. qq{ Copyright (C) 1989, 1991 Free Software Foundation, Inc.  51 Franklin Street, \n }
	. qq{ Fifth Floor, Boston, MA  02110-1301, USA \n }
	. qq{  \n }
	. qq{ Everyone is permitted to copy and distribute verbatim copies of this license \n }
	. qq{ document, but changing it is not allowed. \n }
	. qq{  \n }
	. qq{ Preamble \n }
	. qq{  \n }
	. qq{ The licenses for most software are designed to take away your freedom to share \n }
	. qq{ and change it. By contrast, the GNU General Public License is intended to \n }
	. qq{ guarantee your freedom to share and change free software--to make sure the \n }
	. qq{ software is free for all its users. This General Public License applies to most \n }
	. qq{ of the Free Software Foundation's software and to any other program whose \n }
	. qq{ authors commit to using it. (Some other Free Software Foundation software is \n }
	. qq{ covered by the GNU Lesser General Public License instead.) You can apply it to \n }
	. qq{ your programs, too. \n }
	. qq{  \n }
	. qq{ When we speak of free software, we are referring to freedom, not price. Our \n }
	. qq{ General Public Licenses are designed to make sure that you have the freedom to \n }
	. qq{ distribute copies of free software (and charge for this service if you wish), \n }
	. qq{ that you receive source code or can get it if you want it, that you can change \n }
	. qq{ the software or use pieces of it in new free programs; and that you know you \n }
	. qq{ can do these things. \n }
	. qq{  \n }
	. qq{ To protect your rights, we need to make restrictions that forbid anyone to deny \n }
	. qq{ you these rights or to ask you to surrender the rights. These restrictions \n }
	. qq{ translate to certain responsibilities for you if you distribute copies of the \n }
	. qq{ software, or if you modify it. \n }
	. qq{  \n }
	. qq{ For example, if you distribute copies of such a program, whether gratis or for \n }
	. qq{ a fee, you must give the recipients all the rights that you have. You must make \n }
	. qq{ sure that they, too, receive or can get the source code. And you must show them \n }
	. qq{ these terms so they know their rights. \n }
	. qq{  \n }
	. qq{ We protect your rights with two steps: (1) copyright the software, and (2) \n }
	. qq{ offer you this license which gives you legal permission to copy, distribute \n }
	. qq{ and/or modify the software. \n }
	. qq{  \n }
	. qq{ Also, for each author's protection and ours, we want to make certain that \n }
	. qq{ everyone understands that there is no warranty for this free software. If the \n }
	. qq{ software is modified by someone else and passed on, we want its recipients to \n }
	. qq{ know that what they have is not the original, so that any problems introduced \n }
	. qq{ by others will not reflect on the original authors' reputations. \n }
	. qq{  \n }
	. qq{ Finally, any free program is threatened constantly by software patents. We wish \n }
	. qq{ to avoid the danger that redistributors of a free program will individually \n }
	. qq{ obtain patent licenses, in effect making the program proprietary. To prevent \n }
	. qq{ this, we have made it clear that any patent must be licensed for everyone's \n }
	. qq{ free use or not licensed at all. \n }
	. qq{  \n }
	. qq{ The precise terms and conditions for copying, distribution and modification \n }
	. qq{ follow.  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION \n }
	. qq{  \n }
	. qq{ 0. This License applies to any program or other work which contains a notice \n }
	. qq{ placed by the copyright holder saying it may be distributed under the terms of \n }
	. qq{ this General Public License. The "Program", below, refers to any such program \n }
	. qq{ or work, and a "work based on the Program" means either the Program or any \n }
	. qq{ derivative work under copyright law: that is to say, a work containing the \n }
	. qq{ Program or a portion of it, either verbatim or with modifications and/or \n }
	. qq{ translated into another language. (Hereinafter, translation is included without \n }
	. qq{ limitation in the term "modification".) Each licensee is addressed as "you". \n }
	. qq{  \n }
	. qq{ Activities other than copying, distribution and modification are not covered by \n }
	. qq{ this License; they are outside its scope. The act of running the Program is not \n }
	. qq{ restricted, and the output from the Program is covered only if its contents \n }
	. qq{ constitute a work based on the Program (independent of having been made by \n }
	. qq{ running the Program). Whether that is true depends on what the Program does. \n }
	. qq{  \n }
	. qq{ 1. You may copy and distribute verbatim copies of the Program's source code as \n }
	. qq{ you receive it, in any medium, provided that you conspicuously and \n }
	. qq{ appropriately publish on each copy an appropriate copyright notice and \n }
	. qq{ disclaimer of warranty; keep intact all the notices that refer to this License \n }
	. qq{ and to the absence of any warranty; and give any other recipients of the \n }
	. qq{ Program a copy of this License along with the Program. \n }
	. qq{  \n }
	. qq{ You may charge a fee for the physical act of transferring a copy, and you may \n }
	. qq{ at your option offer warranty protection in exchange for a fee. \n }
	. qq{  \n }
	. qq{ 2. You may modify your copy or copies of the Program or any portion of it, thus \n }
	. qq{ forming a work based on the Program, and copy and distribute such modifications \n }
	. qq{ or work under the terms of Section 1 above, provided that you also meet all of \n }
	. qq{ these conditions: \n }
	. qq{  \n }
	. qq{ a) You must cause the modified files to carry prominent notices stating that \n }
	. qq{ you changed the files and the date of any change.  \n }
	. qq{  \n }
	. qq{ b) You must cause any work that you distribute or publish, that in whole or in \n }
	. qq{ part contains or is derived from the Program or any part thereof, to be \n }
	. qq{ licensed as a whole at no charge to all third parties under the terms of this \n }
	. qq{ License.  \n }
	. qq{  \n }
	. qq{ c) If the modified program normally reads commands interactively when run, you \n }
	. qq{ must cause it, when started running for such interactive use in the most \n }
	. qq{ ordinary way, to print or display an announcement including an appropriate \n }
	. qq{ copyright notice and a notice that there is no warranty (or else, saying that \n }
	. qq{ you provide a warranty) and that users may redistribute the program under these \n }
	. qq{ conditions, and telling the user how to view a copy of this License. \n }
	. qq{ (Exception: if the Program itself is interactive but does not normally print \n }
	. qq{ such an announcement, your work based on the Program is not required to print \n }
	. qq{ an announcement.)  \n }
	. qq{  \n }
	. qq{ These requirements apply to the modified work as a whole. If identifiable \n }
	. qq{ sections of that work are not derived from the Program, and can be reasonably \n }
	. qq{ considered independent and separate works in themselves, then this License, and \n }
	. qq{ its terms, do not apply to those sections when you distribute them as separate \n }
	. qq{ works. But when you distribute the same sections as part of a whole which is a \n }
	. qq{ work based on the Program, the distribution of the whole must be on the terms \n }
	. qq{ of this License, whose permissions for other licensees extend to the entire \n }
	. qq{ whole, and thus to each and every part regardless of who wrote it. \n }
	. qq{  \n }
	. qq{ Thus, it is not the intent of this section to claim rights or contest your \n }
	. qq{ rights to work written entirely by you; rather, the intent is to exercise the \n }
	. qq{ right to control the distribution of derivative or collective works based on \n }
	. qq{ the Program. \n }
	. qq{  \n }
	. qq{ In addition, mere aggregation of another work not based on the Program with the \n }
	. qq{ Program (or with a work based on the Program) on a volume of a storage or \n }
	. qq{ distribution medium does not bring the other work under the scope of this \n }
	. qq{ License. \n }
	. qq{  \n }
	. qq{ 3. You may copy and distribute the Program (or a work based on it, under \n }
	. qq{ Section 2) in object code or executable form under the terms of Sections 1 and \n }
	. qq{ 2 above provided that you also do one of the following: \n }
	. qq{  \n }
	. qq{ a) Accompany it with the complete corresponding machine-readable source code, \n }
	. qq{ which must be distributed under the terms of Sections 1 and 2 above on a medium \n }
	. qq{ customarily used for software interchange; or,  \n }
	. qq{  \n }
	. qq{ b) Accompany it with a written offer, valid for at least three years, to give \n }
	. qq{ any third party, for a charge no more than your cost of physically performing \n }
	. qq{ source distribution, a complete machine-readable copy of the corresponding \n }
	. qq{ source code, to be distributed under the terms of Sections 1 and 2 above on a \n }
	. qq{ medium customarily used for software interchange; or,  \n }
	. qq{  \n }
	. qq{ c) Accompany it with the information you received as to the offer to distribute \n }
	. qq{ corresponding source code. (This alternative is allowed only for noncommercial \n }
	. qq{ distribution and only if you received the program in object code or executable \n }
	. qq{ form with such an offer, in accord with Subsection b above.)  \n }
	. qq{  \n }
	. qq{ The source code for a work means the preferred form of the work for making \n }
	. qq{ modifications to it. For an executable work, complete source code means all the \n }
	. qq{ source code for all modules it contains, plus any associated interface \n }
	. qq{ definition files, plus the scripts used to control compilation and installation \n }
	. qq{ of the executable. However, as a special exception, the source code distributed \n }
	. qq{ need not include anything that is normally distributed (in either source or \n }
	. qq{ binary form) with the major components (compiler, kernel, and so on) of the \n }
	. qq{ operating system on which the executable runs, unless that component itself \n }
	. qq{ accompanies the executable. \n }
	. qq{  \n }
	. qq{ If distribution of executable or object code is made by offering access to copy \n }
	. qq{ from a designated place, then offering equivalent access to copy the source \n }
	. qq{ code from the same place counts as distribution of the source code, even though \n }
	. qq{ third parties are not compelled to copy the source along with the object code. \n }
	. qq{  \n }
	. qq{ 4. You may not copy, modify, sublicense, or distribute the Program except as \n }
	. qq{ expressly provided under this License. Any attempt otherwise to copy, modify, \n }
	. qq{ sublicense or distribute the Program is void, and will automatically terminate \n }
	. qq{ your rights under this License. However, parties who have received copies, or \n }
	. qq{ rights, from you under this License will not have their licenses terminated so \n }
	. qq{ long as such parties remain in full compliance. \n }
	. qq{  \n }
	. qq{ 5. You are not required to accept this License, since you have not signed it. \n }
	. qq{ However, nothing else grants you permission to modify or distribute the Program \n }
	. qq{ or its derivative works. These actions are prohibited by law if you do not \n }
	. qq{ accept this License. Therefore, by modifying or distributing the Program (or \n }
	. qq{ any work based on the Program), you indicate your acceptance of this License to \n }
	. qq{ do so, and all its terms and conditions for copying, distributing or modifying \n }
	. qq{ the Program or works based on it. \n }
	. qq{  \n }
	. qq{ 6. Each time you redistribute the Program (or any work based on the Program), \n }
	. qq{ the recipient automatically receives a license from the original licensor to \n }
	. qq{ copy, distribute or modify the Program subject to these terms and conditions. \n }
	. qq{ You may not impose any further restrictions on the recipients' exercise of the \n }
	. qq{ rights granted herein. You are not responsible for enforcing compliance by \n }
	. qq{ third parties to this License. \n }
	. qq{  \n }
	. qq{ 7. If, as a consequence of a court judgment or allegation of patent \n }
	. qq{ infringement or for any other reason (not limited to patent issues), conditions \n }
	. qq{ are imposed on you (whether by court order, agreement or otherwise) that \n }
	. qq{ contradict the conditions of this License, they do not excuse you from the \n }
	. qq{ conditions of this License. If you cannot distribute so as to satisfy \n }
	. qq{ simultaneously your obligations under this License and any other pertinent \n }
	. qq{ obligations, then as a consequence you may not distribute the Program at all. \n }
	. qq{ For example, if a patent license would not permit royalty-free redistribution \n }
	. qq{ of the Program by all those who receive copies directly or indirectly through \n }
	. qq{ you, then the only way you could satisfy both it and this License would be to \n }
	. qq{ refrain entirely from distribution of the Program. \n }
	. qq{  \n }
	. qq{ If any portion of this section is held invalid or unenforceable under any \n }
	. qq{ particular circumstance, the balance of the section is intended to apply and \n }
	. qq{ the section as a whole is intended to apply in other circumstances. \n }
	. qq{  \n }
	. qq{ It is not the purpose of this section to induce you to infringe any patents or \n }
	. qq{ other property right claims or to contest validity of any such claims; this \n }
	. qq{ section has the sole purpose of protecting the integrity of the free software \n }
	. qq{ distribution system, which is implemented by public license practices. Many \n }
	. qq{ people have made generous contributions to the wide range of software \n }
	. qq{ distributed through that system in reliance on consistent application of that \n }
	. qq{ system; it is up to the author/donor to decide if he or she is willing to \n }
	. qq{ distribute software through any other system and a licensee cannot impose that \n }
	. qq{ choice. \n }
	. qq{  \n }
	. qq{ This section is intended to make thoroughly clear what is believed to be a \n }
	. qq{ consequence of the rest of this License. \n }
	. qq{  \n }
	. qq{ 8. If the distribution and/or use of the Program is restricted in certain \n }
	. qq{ countries either by patents or by copyrighted interfaces, the original \n }
	. qq{ copyright holder who places the Program under this License may add an explicit \n }
	. qq{ geographical distribution limitation excluding those countries, so that \n }
	. qq{ distribution is permitted only in or among countries not thus excluded. In such \n }
	. qq{ case, this License incorporates the limitation as if written in the body of \n }
	. qq{ this License. \n }
	. qq{  \n }
	. qq{ 9. The Free Software Foundation may publish revised and/or new versions of the \n }
	. qq{ General Public License from time to time. Such new versions will be similar in \n }
	. qq{ spirit to the present version, but may differ in detail to address new problems \n }
	. qq{ or concerns. \n }
	. qq{  \n }
	. qq{ Each version is given a distinguishing version number. If the Program specifies \n }
	. qq{ a version number of this License which applies to it and "any later version", \n }
	. qq{ you have the option of following the terms and conditions either of that \n }
	. qq{ version or of any later version published by the Free Software Foundation. If \n }
	. qq{ the Program does not specify a version number of this License, you may choose \n }
	. qq{ any version ever published by the Free Software Foundation. \n }
	. qq{  \n }
	. qq{ 10. If you wish to incorporate parts of the Program into other free programs \n }
	. qq{ whose distribution conditions are different, write to the author to ask for \n }
	. qq{ permission. For software which is copyrighted by the Free Software Foundation, \n }
	. qq{ write to the Free Software Foundation; we sometimes make exceptions for this. \n }
	. qq{ Our decision will be guided by the two goals of preserving the free status of \n }
	. qq{ all derivatives of our free software and of promoting the sharing and reuse of \n }
	. qq{ software generally. \n }
	. qq{  \n }
	. qq{ NO WARRANTY \n }
	. qq{  \n }
	. qq{ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR \n }
	. qq{ THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE \n }
	. qq{ STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE \n }
	. qq{ PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, \n }
	. qq{ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND \n }
	. qq{ FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND \n }
	. qq{ PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU \n }
	. qq{ ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. \n }
	. qq{  \n }
	. qq{ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL \n }
	. qq{ ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE \n }
	. qq{ PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY \n }
	. qq{ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR \n }
	. qq{ INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA \n }
	. qq{ BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A \n }
	. qq{ FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER \n }
	. qq{ OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. \n }
;

	print $licenseText;
}