<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Untitled &#187; sysadmin</title>
	<atom:link href="http://ammonlauritzen.com/blog/category/sysadmin/feed/" rel="self" type="application/rss+xml" />
	<link>http://ammonlauritzen.com/blog</link>
	<description>and still for good reason.</description>
	<lastBuildDate>Wed, 05 May 2010 18:43:20 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>virtualbox rules</title>
		<link>http://ammonlauritzen.com/blog/2010/03/10/virtualbox-rules/</link>
		<comments>http://ammonlauritzen.com/blog/2010/03/10/virtualbox-rules/#comments</comments>
		<pubDate>Wed, 10 Mar 2010 21:42:23 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[personal]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=1111</guid>
		<description><![CDATA[Nothing much to say here, but with absolutely minimal pain and suffering, I have 64-bit linux virtual machines running on top of my 32-bit windows XP install. This pleases me.
The recipe:

Compatible CPU with VT-x/AMD-V enabled in the BIOS
Innotek/Oracle/Sun VirtualBox (a current version) with hardware virtualization enabled
Profit!

The one downside to this? 64-bit VM&#8217;s running on 32-bit [...]]]></description>
			<content:encoded><![CDATA[<p>Nothing much to say here, but with absolutely minimal pain and suffering, I have 64-bit linux virtual machines running on top of my 32-bit windows XP install. This pleases me.</p>
<p>The recipe:</p>
<ol>
<li>Compatible CPU with VT-x/AMD-V enabled in the BIOS</li>
<li>Innotek/Oracle/Sun VirtualBox (a current version) with hardware virtualization enabled</li>
<li>Profit!</li>
</ol>
<p>The one downside to this? 64-bit VM&#8217;s running on 32-bit host OS can&#8217;t see multiple cpu&#8217;s. Boo. Hoo. I&#8217;ll just run more VM&#8217;s!</p>
<p><a href="http://ammonlauritzen.com/blog/wp-content/uploads/2010/03/centos-installer-64-virtualbox.png"><img src="http://ammonlauritzen.com/blog/wp-content/uploads/2010/03/centos-installer-64-virtualbox-300x250.png" alt="64-bit centos installer" title="centos-installer-64-virtualbox" width="300" height="250" class="alignnone size-medium wp-image-1112" /></a> <a href="http://ammonlauritzen.com/blog/wp-content/uploads/2010/03/ubuntu-64-virtualbox.png"><img src="http://ammonlauritzen.com/blog/wp-content/uploads/2010/03/ubuntu-64-virtualbox-300x250.png" alt="64-bit ubuntu livecd" title="ubuntu-64-virtualbox" width="300" height="250" class="alignnone size-medium wp-image-1113" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2010/03/10/virtualbox-rules/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>dead simple traditional style rotation</title>
		<link>http://ammonlauritzen.com/blog/2010/02/05/dead-simple-traditional-style-rotation/</link>
		<comments>http://ammonlauritzen.com/blog/2010/02/05/dead-simple-traditional-style-rotation/#comments</comments>
		<pubDate>Fri, 05 Feb 2010 17:51:26 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[programming]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[logfiles]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=1105</guid>
		<description><![CDATA[In response to my two-step rotation post earlier this week, I figure I may as well share the logic I use for a more traditional logfile rotation scheme.
I think this is as simple as I can possibly make it:

&#60;?
define( 'MAX_COPIES', 3 );
$back_fname = &#34;/path/to/log/file/abc.log&#34;;

function trace( $msg ) {
    echo &#34;- $msg\n&#34;;
}

exec( &#34;ls [...]]]></description>
			<content:encoded><![CDATA[<p>In response to <a href='http://ammonlauritzen.com/blog/2010/02/03/simple-two-step-logfile-rotation/'>my two-step rotation post</a> earlier this week, I figure I may as well share the logic I use for a more traditional logfile rotation scheme.</p>
<p>I think this is as simple as I can possibly make it:</p>
<pre class="brush: php;">
&lt;?
define( 'MAX_COPIES', 3 );
$back_fname = &quot;/path/to/log/file/abc.log&quot;;

function trace( $msg ) {
    echo &quot;- $msg\n&quot;;
}

exec( &quot;ls -r ${back_fname}*&quot;, $copies, $succ );
while( count($copies) &gt;= MAX_COPIES ) {
    $fname = array_shift($copies);
    trace( &quot;deleting &quot;.$fname );
}
$next = count($copies);
while( $fname = array_shift($copies) ) {
    --$next;
    trace( &quot;rotating $fname -&gt; $next&quot; );
    rename( $fname, &quot;$back_fname.$next&quot; );
}

trace( &quot;creating $back_fname&quot; );
touch( $back_fname );
?&gt;
</pre>
<p>A sample series of executions might look like this:</p>
<pre class="brush: plain;">
ammon@wernstrom:/path/to/log/file$ touch abc.log
ammon@wernstrom:/path/to/log/file$ php rotate.php
- rotating /path/to/log/file/abc.log -&gt; 0
- creating /path/to/log/file/abc.log
ammon@wernstrom:/path/to/log/file$ php rotate.php
- rotating /path/to/log/file/abc.log.0 -&gt; 1
- rotating /path/to/log/file/abc.log -&gt; 0
- creating /path/to/log/file/abc.log
ammon@wernstrom:/path/to/log/file$ php rotate.php
- rotating /path/to/log/file/abc.log.1 -&gt; 2
- rotating /path/to/log/file/abc.log.0 -&gt; 1
- rotating /path/to/log/file/abc.log -&gt; 0
- creating /path/to/log/file/abc.log
ammon@wernstrom:/path/to/log/file$ php rotate.php
- deleting /path/to/log/file/abc.log.2
- rotating /path/to/log/file/abc.log.1 -&gt; 2
- rotating /path/to/log/file/abc.log.0 -&gt; 1
- rotating /path/to/log/file/abc.log -&gt; 0
- creating /path/to/log/file/abc.log
</pre>
<p>This doesn&#8217;t have any failsafes, doesn&#8217;t compress anything, depends on an external call to &#8216;ls&#8217;, and it actually deletes old files in stead of overwriting them&#8230; but it is the shortest, simplest method I&#8217;ve come up with to get the job done.</p>
<p>If I feel like making this a full-fledged series, I might actually post a more thorough implementation later <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2010/02/05/dead-simple-traditional-style-rotation/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>simple two-step logfile rotation</title>
		<link>http://ammonlauritzen.com/blog/2010/02/03/simple-two-step-logfile-rotation/</link>
		<comments>http://ammonlauritzen.com/blog/2010/02/03/simple-two-step-logfile-rotation/#comments</comments>
		<pubDate>Wed, 03 Feb 2010 19:23:08 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[programming]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[logfiles]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=1103</guid>
		<description><![CDATA[This is the result of 10 minutes of pounding on the keyboard after yet another disappointing experience with trying to get logrotate to do something vaguely more flexible.
This simple script scans all normal files in a log directory, and if they are older than a certain cutoff, moves them into a holding directory for old [...]]]></description>
			<content:encoded><![CDATA[<p>This is the result of 10 minutes of pounding on the keyboard after yet another disappointing experience with trying to get logrotate to do something vaguely more flexible.</p>
<p>This simple script scans all normal files in a log directory, and if they are older than a certain cutoff, moves them into a holding directory for old logs. Future passes will check files in the old directory for another age setting and will delete them. That&#8217;s all there is to it.</p>
<p>Configure your cutoffs, directories of interest, and optionally plug in a better logging mechanism and you&#8217;re set. (Oh, and change the #! if necessary, of course).</p>
<pre class="brush: php;">
#!/usr/bin/php
&lt;?
$cutoff_rotate = &quot;3 days&quot;;
$cutoff_delete = &quot;7 days&quot;;
$dir_log = &quot;/logs&quot;;
$dir_old = &quot;/logs.old&quot;;

function trace( $msg, $debug = FALSE ) {
	// appropriate logging mechanism can be plugged in here
	echo &quot;[] $msg\n&quot;;
}

// scan old files for deletion
if( is_dir($dir_old) ) {
	$dh = opendir( $dir_old );
	if( $dh !== FALSE ) {
		chdir( $dir_old );
		trace( &quot;scanning $dir_old for logs more than $cutoff_delete old&quot; );
		$cutoff = strtotime( &quot;-$cutoff_delete&quot; );
		trace( &quot;cutoff is &quot;.date('r',$cutoff) );
		while( ($file = readdir($dh)) !== FALSE ) {
			if( is_dir($file) ) {
				trace( &quot;skipping $file&quot;, true );
				continue;
			} else {
				$ts = filemtime($file);
				if( $ts &lt; $cutoff ) {
					trace( &quot;deleting $file, &quot;.date('r',$ts) );
					$succ = @unlink($file);
					if( !$succ )
						trace( &quot;failed to unlink $file!&quot; );
				} else {
					trace( &quot;ignoring $file, &quot;.date('r',$ts), true );
				}
			}
		}
	}
	closedir( $dh );
} else {
	trace( &quot;no old log dir $dir_old to scan yet&quot; );
}

// scan current files for rotation
if( file_exists($dir_old) ) {
	trace( &quot;creating $dir_old&quot; );
	$succ = @mkdir( $dir_old, 0775, true );
	if( !$succ ) {
		trace( &quot;mkdir failed, aborting rotation&quot; );
		exit( 1 );
	}
}
if( is_dir($dir_log) ) {
	$dh = opendir( $dir_log );
	if( $dh !== FALSE ) {
		chdir( $dir_log );
		trace( &quot;scanning $dir_log for logs more than $cutoff_rotate old&quot; );
		$cutoff = strtotime( &quot;-$cutoff_delete&quot; );
		trace( &quot;cutoff is &quot;.date('r',$cutoff) );
		while( ($file = readdir($dh)) !== FALSE ) {
			if( is_dir($file) ) {
				trace( &quot;skipping $file&quot;, true );
				continue;
			} else {
				$ts = filemtime($file);
				if( $ts &lt; $cutoff ) {
					trace( &quot;rotating $file, &quot;.date('r',$ts), true );
					$succ = @rename( $file, $dir_old );
					if( !$succ )
						trace( &quot;failed to rotate $file!&quot; );
				} else {
					trace( &quot;ignoring $file, &quot;.date('r',$ts), true );
				}
			}
		}
	}
	closedir( $dh );
}
?&gt;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2010/02/03/simple-two-step-logfile-rotation/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>report-storage.sh</title>
		<link>http://ammonlauritzen.com/blog/2009/10/14/report-storagesh/</link>
		<comments>http://ammonlauritzen.com/blog/2009/10/14/report-storagesh/#comments</comments>
		<pubDate>Wed, 14 Oct 2009 20:55:13 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[programming]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=552</guid>
		<description><![CDATA[In the grand tradition of my publishing little building-block shell scripts of interest, here goes another one. This is a simple cron job that I run daily on a number of hosts to generate storage usage growth. (This is in addition to Cacti and Nagios which poll some of this data already but for different [...]]]></description>
			<content:encoded><![CDATA[<p>In the grand tradition of my publishing little building-block shell scripts of interest, here goes another one. This is a simple cron job that I run daily on a number of hosts to generate storage usage growth. (This is in addition to Cacti and Nagios which poll some of this data already but for different reasons and with different granularity).</p>
<p>The FILES variable should be populated with a whitespace separated list of files, directories, and block devices to track.</p>
<p>The DB_ABCD variables should be populated with appropriate credentials to talk to a mysql server.</p>
<p>The actual script looks something like this:</p>
<pre class="brush: bash;">
#!/bin/bash

FILES='/var/lib/mysql/ibdata1 /var/lib/mysql/db/table.ibd /dev/sda1 /var/log/mysql'

LOCAL=`hostname -s`
DB_HOST='aaa'
DB_USER='bbb'
DB_PASS='ccc'

function insert {
    FILE=$1
    SIZE=$2
    QUERY=&quot;replace into metrics.storage_usage values( now(), '$LOCAL', '$FILE', $SIZE )&quot;
    mysql --host=${DB_HOST} --user=${DB_USER} --password=&quot;${DB_PASS}&quot; -e &quot;${QUERY}&quot;
}

for FILE in $FILES
do
    if [ -d $FILE ]; then
        BASE=$FILE
        SIZE=`du -ks $FILE/ | awk '{print $1}'`
    elif [ -b $FILE ]; then
        TMP=`df -k -P $FILE | tail -n1 | awk '{print $3 &quot; &quot; $6}'`
        SIZE=`echo $TMP | awk '{print $1}'`
        BASE=`echo $TMP | awk '{print $2}'`
    else
        BASE=`basename $FILE`
        SIZE=`du -k $FILE | awk '{print $1}'`
    fi

    echo &quot;$BASE = $SIZE&quot;
    insert $BASE $SIZE
done
</pre>
<p>I am putting my data into a table called &#8220;storage_usage&#8221; in a database called &#8220;metrics&#8221;:</p>
<pre class="brush: sql;">
CREATE TABLE `storage_usage` (
  `ts` date NOT NULL,
  `host` varchar(25) NOT NULL,
  `file` varchar(64) NOT NULL,
  `size` int(10) unsigned NOT NULL COMMENT 'in kbytes',
  PRIMARY KEY (`ts`,`host`,`file`)
)
</pre>
<p>Obviously, this could be tweaked in any different number of ways, based on your needs. One tweak you might want to consider if you&#8217;re running it in a daily cron is to remove the echo so you don&#8217;t get an email report of every run. Also, if you might want to record more than one snapshot per file per host per day &#8211; in the which case you probably need to change the type of the timestamp column to a datetime. Or there might be cases where you want to change the replace to an insert or&#8230; whatever <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2009/10/14/report-storagesh/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>xtrabackup innodb_file_per_table gotcha</title>
		<link>http://ammonlauritzen.com/blog/2009/08/18/xtrabackup-innodb_file_per_table-gotcha/</link>
		<comments>http://ammonlauritzen.com/blog/2009/08/18/xtrabackup-innodb_file_per_table-gotcha/#comments</comments>
		<pubDate>Tue, 18 Aug 2009 17:55:03 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[gotcha]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[innodb]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[xtrabackup]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=540</guid>
		<description><![CDATA[Several months ago, I&#8217;d switched to using Percona&#8217;s xtrabackup &#038; innobackupex for all of my mysql backup needs. I had successfully used these backups to restore and replicate databases across several systems. It is good stuff.
Last week, I needed to set up new replication of an 80gb database. This should have been routine by now, [...]]]></description>
			<content:encoded><![CDATA[<p>Several months ago, I&#8217;d switched to using Percona&#8217;s xtrabackup &#038; innobackupex for all of my mysql backup needs. I had successfully used these backups to restore and replicate databases across several systems. It is good stuff.</p>
<p>Last week, I needed to set up new replication of an 80gb database. This should have been routine by now, but when I attempted to prepare the backup this time, it whined and complained and failed. I was kind of frazzled by the time I gave up on the issue and declared it a fluke of one sort or another.</p>
<p>Last night, I tried again from Sunday&#8217;s full backup, and it happened again:</p>
<pre>
ammon@amy:/var/lib/2009-08-16_04-02-17$ sudo xtrabackup --prepare --target-dir=.
xtrabackup  Ver 0.8.1rc Rev 78 for 5.0.83 unknown-linux-gnu (x86_64)
xtrabackup: cd to .
xtrabackup: This target seems to be not prepared yet.
xtrabackup: xtrabackup_logfile detected: size=75546624, start_lsn=(86 1293090752)
xtrabackup: Temporary instance for recovery is set as followings.
xtrabackup:   innodb_data_home_dir = ./
xtrabackup:   innodb_data_file_path = ibdata1:512M:autoextend
xtrabackup:   innodb_log_group_home_dir = ./
xtrabackup:   innodb_log_files_in_group = 1
xtrabackup:   innodb_log_file_size = 75546624
xtrabackup: Starting InnoDB instance for recovery.
xtrabackup: Using 104857600 bytes for buffer pool (set by --use-memory parameter)
InnoDB: Log scan progressed past the checkpoint lsn 86 1293090752
090818  2:54:34  InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
090818  2:54:34  InnoDB: Operating system error number 2 in a file operation.
InnoDB: The error means the system cannot find the path specified.
InnoDB: If you are installing InnoDB, remember that you must create
InnoDB: directories yourself, InnoDB does not create them.
InnoDB: File name .//tmp/#sql6e1e_8cce1_0.ibd
InnoDB: File operation call: 'create'.
InnoDB: Cannot continue operation.
</pre>
<p>I gave up after poking a few things.</p>
<p>This morning&#8217;s fresh look turned up <a href='http://bugs.mysql.com/bug.php?id=41609'>this bug report</a>.</p>
<pre>
ammon@amy:/var/lib/2009-08-16_04-02-17$ sudo <b>mkdir tmp</b>
ammon@amy:/var/lib/2009-08-16_04-02-17$ sudo xtrabackup --prepare --target-dir=.
xtrabackup  Ver 0.8.1rc Rev 78 for 5.0.83 unknown-linux-gnu (x86_64)
xtrabackup: cd to .
xtrabackup: This target seems to be not prepared yet.
090818 12:27:41  InnoDB: Operating system error number 2 in a file operation.
InnoDB: The error means the system cannot find the path specified.
xtrabackup: Warning: cannot open ./xtrabackup_logfile. will try to find.
xtrabackup: 'ib_logfile0' seems to be 'xtrabackup_logfile'. will retry.
xtrabackup: xtrabackup_logfile detected: size=84983808, start_lsn=(86 1293090752)
xtrabackup: Temporary instance for recovery is set as followings.
xtrabackup:   innodb_data_home_dir = ./
xtrabackup:   innodb_data_file_path = ibdata1:512M:autoextend
xtrabackup:   innodb_log_group_home_dir = ./
xtrabackup:   innodb_log_files_in_group = 1
xtrabackup:   innodb_log_file_size = 84983808
xtrabackup: Starting InnoDB instance for recovery.
xtrabackup: Using 104857600 bytes for buffer pool (set by --use-memory parameter)
InnoDB: Log scan progressed past the checkpoint lsn 86 1293090752
090818 12:27:41  InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Doing recovery: scanned up to log sequence number 86 1298333184 (6 %)
InnoDB: Doing recovery: scanned up to log sequence number 86 1303576064 (13 %)
InnoDB: Doing recovery: scanned up to log sequence number 86 1308818944 (20 %)
InnoDB: Doing recovery: scanned up to log sequence number 86 1314061824 (27 %)
InnoDB: Doing recovery: scanned up to log sequence number 86 1319304704 (34 %)
090818 12:27:44  InnoDB: Starting an apply batch of log records to the database...
<i>... snip ...</i>
</pre>
<p>That&#8217;s right. There&#8217;s a bug in innodb restoration that interprets location of /tmp (configurable in my.cnf) to be relative in stead of absolute.</p>
<p>So, if you have problems while trying to restore from an xtrabackup/ibbackup snapshot (or if you&#8217;re trying to recover innodb after a crash), just creating the offending tmp directory appears to work.</p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2009/08/18/xtrabackup-innodb_file_per_table-gotcha/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>init.d template</title>
		<link>http://ammonlauritzen.com/blog/2009/06/18/initd-template/</link>
		<comments>http://ammonlauritzen.com/blog/2009/06/18/initd-template/#comments</comments>
		<pubDate>Thu, 18 Jun 2009 17:29:31 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[programming]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[init.d]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=512</guid>
		<description><![CDATA[This is a rudimentary template that I&#8217;ve been using for very quick and dirty /etc/init.d scripts recently.
It works under the assumption that your server daemon has a unique name and only ever runs a single instance &#8211; this also means that the binary and the init.d script cannot share a name &#8211; otherwise strange things [...]]]></description>
			<content:encoded><![CDATA[<p>This is a rudimentary template that I&#8217;ve been using for very quick and dirty /etc/init.d scripts recently.</p>
<p>It works under the assumption that your server daemon has a unique name and only ever runs a single instance &#8211; this also means that the binary and the init.d script cannot share a name &#8211; otherwise strange things happen <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>Actual invocation logic may need to be updated on a per-service basis and chkconfig style headers would have to be added manually, but it works well for what it is.</p>
<pre class="brush: bash;">
#!/bin/bash

DIR=''	# path to the daemon executable
CMD=''	# name of the command itself
ARG=''	# optional. any arguments to pass when starting
NAM=''	# descriptive name of the daemon so it shows up pretty

function get_ps {
	ps --no-header -C${CMD}
}

function do_start {
	echo -n &quot;Starting ${NAM}... &quot;
	cd ${DIR}
	nohup ./${CMD} ${ARG} &amp;
	SUCC=`get_ps | wc -l`
	if [ &quot;1&quot; == &quot;$SUCC&quot; ]; then
		echo &quot;[SUCCESS]&quot;
	else
		echo &quot;[FAILURE]&quot;
	fi
}

function do_stop {
	echo -n &quot;Stopping ${NAM}... &quot;
	PID=`get_ps | awk '{print $1}'`
	kill $PID
	SUCC=`get_ps | wc -l`
	if [ &quot;0&quot; == &quot;$SUCC&quot; ]; then
		echo &quot;[SUCCESS]&quot;
	else
		echo &quot;[FAILURE]&quot;
	fi
}

case &quot;${1:-''}&quot; in
	'start')
		do_start
		;;
	'stop')
		do_stop
		;;
	'restart')
		do_stop
		do_start
		;;
	*)
		#echo &quot;Usage: $SELF start|stop|restart|reload|force-reload|status&quot;
		echo &quot;Usage: $SELF start|stop|restart&quot;
		exit 1
		;;
esac
</pre>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2009/06/18/initd-template/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>i </title>
		<link>http://ammonlauritzen.com/blog/2009/02/04/i-heart-rsync/</link>
		<comments>http://ammonlauritzen.com/blog/2009/02/04/i-heart-rsync/#comments</comments>
		<pubDate>Wed, 04 Feb 2009 22:07:39 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[personal]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[rsync]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=448</guid>
		<description><![CDATA[I really love rsync. I&#8217;ll get to the specifics later, but first, the excessive backstory.
I&#8217;ve been doing a lot of backup scripting recently. Yes, there are tons of commercial apps out there, but none of them that I&#8217;ve looked into are a perfect match for all of our needs. I&#8217;ll eventually settle on one and [...]]]></description>
			<content:encoded><![CDATA[<p>I really love rsync. I&#8217;ll get to the specifics later, but first, the excessive backstory.</p>
<p>I&#8217;ve been doing a lot of backup scripting recently. Yes, there are tons of commercial apps out there, but none of them that I&#8217;ve looked into are a perfect match for all of our needs. I&#8217;ll eventually settle on one and it will probably replace 80% of my scripts, but plenty will remain.</p>
<p>One problem that I&#8217;ve encountered while doing the whole backup juggling bit is the ferocious rate of change in the nature of the data we&#8217;re archiving. Code I&#8217;d written a year ago was obsoleted 6 months ago by code that was obsoleted 3 months ago by code that I replaced a few weeks back that is being replaced by the code I&#8217;m writing right now.</p>
<p>Another one of the problems is that the sheer quantity of data involved is growing in a very uncontrolled way. Early last May (the oldest archive I have easy access to), a full archive of the entire system was barely 3gb in size. Today, it is closer to 60gb. ~20x growth over the last 9 months.</p>
<p>It&#8217;s been fun, if somewhat frustrating, dealing with all of the growth.</p>
<p>On January 20th, I needed to perform a long-deprecated sort of snapshot. The code that generated this sort of file no longer worked because so many things had changed. I wound up digging out old scripts from SVN and updating them to run against the new environment.</p>
<p>Because of the amount of data involved, this took a very long time. It didn&#8217;t help that the scripts consumed an unfair amount of system resources &#8211; I couldn&#8217;t run them with any meaningful priority during the day without crippling everyone else.</p>
<p>Lots of low priority io later, I finally had a 54gb tar file&#8230; In one of the three places I needed it.</p>
<p>The first transfer was simple, the hosts are on the same gigabit switch as each other. Unfortunately, scping that much data between two hosts at that kind of speed has negative effects on the systems involved. I had to throttle the transfer way down to before it could run without visibly impacting performance.</p>
<pre class="brush: plain;">
rsync --partial --bwlimit=10000 -e &quot;ssh -i ${RSA_KEYFILE}&quot; ${LOCAL_FNAME} {REMOTE_USER}@${REMOTE_HOST}:${REMOTE_FNAME}
</pre>
<p>The second transfer&#8230; wasn&#8217;t so easy. I needed to move the file to my office without negatively impacting everyone&#8217;s ability to work &#8211; and I couldn&#8217;t wait for the transfer to run at low enough speeds not to cripple the T1.</p>
<p>We have a backup 6mbit DSL link that I only use for emergencies and for testing. Even at a full 6mbit, the transfer would have taken more than 36 hours. Compressing the file took a while but brought the file size down to a much more manageable 24gb (~11 hours over the DSL).</p>
<p>The only remaining gotcha was that DSL link can&#8217;t actually SSH through the firewall into the colo <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  So&#8230; I started the transfer over https last night and went home.</p>
<p>This morning, it was finally time to decompress the monstrosity locally, but I noticed a hiccup in dsl traffic overnight and figured I&#8217;d run a check on things first &#8211; just to make sure that http resume had worked correctly.</p>
<pre class="brush: plain;">
ammon@scruffy:~$ gunzip --test archive_2009_01_20.tar.gz
gunzip: archive_2009_01_20.tar.gz: invalid compressed data--format violated
</pre>
<p>This was not good. I had a 24gb file that was somehow corrupted&#8230; <u>somewhere</u>.</p>
<p>Since re-downloading the whole thing would cost me another whole day&#8230; I had to find out a way to repair the file in a reasonable amount of time. Some research and suggestion gathering later, it was confirmed that rsync would probably handle the task.</p>
<p>Assuming that I wouldn&#8217;t be using an unfair amount of bandwidth for this, I switched back to the T1 link so I could tunnel through SSH again.</p>
<pre class="brush: plain;">
ammon@scruffy:~$ rsync --checksum --inplace -e &quot;ssh&quot; wernstrom:/tmp/archive_2009_01_20.tar.gz archive_2009_01_20.tar.gz 

sent 1280578 bytes  received 1440757 bytes  2622.97 bytes/sec
total size is 25619572576  speedup is 9414.34

ammon@scruffy:~$ gunzip --test archive_2009_01_20.tar.gz
ammon@scruffy:~$</pre>
<p><i>(remember, this is unix, no output implies success)</i></p>
<p>So, yeah. Rsync, I love it when you work. <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>It took some time and generated a lot of disk activity when the process started, but it worked almost painlessly and only transferred the data I needed &#8211; thus leaving the shared network resource free for everyone else <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2009/02/04/i-heart-rsync/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ccent</title>
		<link>http://ammonlauritzen.com/blog/2008/07/02/ccent/</link>
		<comments>http://ammonlauritzen.com/blog/2008/07/02/ccent/#comments</comments>
		<pubDate>Wed, 02 Jul 2008 21:25:20 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[personal]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[cisco]]></category>
		<category><![CDATA[networking]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=390</guid>
		<description><![CDATA[So&#8230; I just finished the first half of my CCNA today.
I never really cared about networking much beyond that needed to make sure clients on a lan can talk to their dns server&#8230; but we&#8217;ve been growing enough here at work that the needs quickly outpaced my prior skillset. And since I was the closest [...]]]></description>
			<content:encoded><![CDATA[<p>So&#8230; I just finished the <a href='http://www.cisco.com/web/learning/le3/current_exams/640-822.html'>first half</a> of my <a href='http://www.cisco.com/web/learning/le3/le2/le0/le9/learning_certification_type_home.html'>CCNA</a> today.</p>
<p>I never really cared about networking much beyond that needed to make sure clients on a lan can talk to their dns server&#8230; but we&#8217;ve been growing enough here at work that the needs quickly outpaced my prior skillset. And since I was the closest thing we had to a network admin, I got signed up for classes <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>It&#8217;s been fun and profoundly enlightening. I didn&#8217;t expect to have my way of thinking so radically altered, but I&#8217;m hardly complaining.</p>
<p>I&#8217;ll be starting up the <a href='http://www.cisco.com/web/learning/le3/current_exams/640-816.html'>second class</a> in a week or two. This&#8217;ll include such topics as VLANs, IPv6, and fancy routing protocols. I&#8217;m stoked.</p>
<p>It&#8217;s funny. I never finished college (though I took classes for roughly 10 years), so this is actually the first certificate of education I&#8217;ve received since highschool.</p>
<p>The comment was made at work that I&#8217;d dinged as a sysadmin. I haven&#8217;t. I&#8217;m just cherry picking my next few levels in netadmin <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2008/07/02/ccent/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>svn get revision</title>
		<link>http://ammonlauritzen.com/blog/2008/05/27/svn-get-revision/</link>
		<comments>http://ammonlauritzen.com/blog/2008/05/27/svn-get-revision/#comments</comments>
		<pubDate>Tue, 27 May 2008 19:57:04 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[subversion]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=388</guid>
		<description><![CDATA[One of the more annoying things about svn is that (to my knowledge), there exists no single simple command to retrieve the revision number from a shell.
What I want:

ammon@hermes:~/repo$ svn info --get-revision .
1234

But of course, nothing like this exists.
Thankfully, svn info&#8217;s output IS easy enough to parse. You just have to do it your self.

ammon@hermes:~/repo$ [...]]]></description>
			<content:encoded><![CDATA[<p>One of the more annoying things about svn is that (to my knowledge), there exists no single simple command to retrieve the revision number from a shell.</p>
<p>What I want:</p>
<pre class="brush: plain;">
ammon@hermes:~/repo$ svn info --get-revision .
1234
</pre>
<p>But of course, nothing like this exists.</p>
<p>Thankfully, svn info&#8217;s output IS easy enough to parse. You just have to do it your self.</p>
<pre class="brush: plain;">
ammon@hermes:~/repo$ svn info | grep Revision | awk -- '{print $2}'
1234
</pre>
<p>Will give you the revision of your current checkout without the network hit of a call to svn log.</p>
<p>To get the current version of the repo itself (hits the network), add &#8220;-r HEAD&#8221; to the svn info call:</p>
<pre class="brush: plain;">
ammon@hermes:~/repo$ svn info -r HEAD | grep Revision | awk -- '{print $2}'
1280
</pre>
<p>Of course, svn info also supports outputting info as xml, so you could use that to parse things in a more advanced environment but one where you&#8217;re still not using the svn api bindings.</p>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2008/05/27/svn-get-revision/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>flash policy service daemon</title>
		<link>http://ammonlauritzen.com/blog/2008/04/22/flash-policy-service-daemon/</link>
		<comments>http://ammonlauritzen.com/blog/2008/04/22/flash-policy-service-daemon/#comments</comments>
		<pubDate>Tue, 22 Apr 2008 09:05:10 +0000</pubDate>
		<dc:creator>Ammon</dc:creator>
				<category><![CDATA[programming]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[flash]]></category>
		<category><![CDATA[networking]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[sockets]]></category>

		<guid isPermaLink="false">http://ammonlauritzen.com/blog/?p=362</guid>
		<description><![CDATA[Sorry it took me so long to post this, but Wordpress 2.5 doesn&#8217;t seem to like me trying to upload gz/zip files, so I had to upload the source manually.
Well, it&#8217;s been months since I promised to post some usable socket policy service code, so I will.
The script here is meant to serve as a [...]]]></description>
			<content:encoded><![CDATA[<p><i>Sorry it took me so long to post this, but Wordpress 2.5 doesn&#8217;t seem to like me trying to upload gz/zip files, so I had to upload the source manually.</i></p>
<p>Well, it&#8217;s been months since <a href='http://ammonlauritzen.com/blog/2007/12/13/new-flash-security-policies/'>I promised</a> to post some usable socket policy service code, so I will.</p>
<p>The script here is meant to serve as a good starting point for people whose servers need to allow flash clients to make socket connections. I have not actually used this exact code in a production environment, but I have been using code that is 99% identical for a while now. I am confident that any blatant flaws are the result of simple copy-paste errors as I compiled the package. Please let me know if you find any.</p>
<p>I <u>have</u> however, stress tested the heck out of this service. One instance successfully served up over 16000 policy file requests fed into it as rapidly as I could send them. The same networking code has also handled requests from at least 100 different hosts at roughly the same time.</p>
<p>Everything has been combined into a single cli php script that requires no special installation. Just plop it down on the server and run it as root. It will take care of the rest. The config defaults should be safe, but you probably want to specify them more clearly &#8211; just to be safe.</p>
<p>The daemon is made of three classes:</p>
<ul>
<li><b>Logger</b> &#8211; A rudimentary log file management class that I copy from project to project in one form or another. The included version is stripped down from some of the other versions I&#8217;ve written, and I&#8217;m planning on releasing a more feature-rich version in the future.</li>
<li><b>Daemon</b> &#8211; A simple class for daemonizing a process. Adapted and re-adapted countless times from an original php4 class I found on the net a few years ago by some guy named Seth (whose email domain no longer exists).</li>
<li><b>FlashPolicyService</b> &#8211; The meat and potatoes, a child of Daemon. Mostly, this is just the requisite networking code and glue to make everything work together.</li>
</ul>
<p>As with any of my other code, this is licensed under <a href='http://creativecommons.org/licenses/by/3.0/'>CC Attribution 3.0</a>.</p>
<p>Download:</p>
<ul>
<li><a href='http://ammonlauritzen.com/FlashPolicyService-09c.zip'>FlashPolicyService-09c.zip</a> (4.4kb)</li>
</ul>
<p>Source code after the jump.<br />
<span id="more-362"></span></p>
<pre class="brush: php;">
#!/usr/local/bin/php
&lt;?php
/**
 * Flash Policy Service v0.9.c
 *
 * This script listens for &lt;policy-file-request/&gt; on port 843 and serves up
 * an xml crossdomain policy file. This sort of service is necessary if any
 * flash content is going to connect to sockets on the running host.
 *
 * I have made every effort to package everything you need into a single
 * file here, even though it could easily have been split into 3 or more
 * scripts.
 *
 * Requirements:
 *  - PHP 5 with sockets, pcntl, and posix extensions
 *  - Root access (to bind to a &lt;1024 port)
 *
 * Use:
 *  Simply execute the script from the command line as root:
 *    # ./FlashPolicyService.php
 *  You can enable debug mode by invoking the script with '-d' as a parameter:
 *    # ./FlashPolicyService.php -d
 *  To stop the daemon, simply send it a SIGTERM and it should attempt to
 *  exit cleanly.
 *
 *  If you get 'bad interpreter' errors or the like, change the #! line to
 *  reflect the actual installed location of your php cli.
 *
 * Configuration:
 *  At present, there aren't very many config options. Simply edit the
 *  section immediately following this header. Options are commented.
 *
 * License:
 *  This code is made available under a Creative Commons Attribution 3.0
 *  License. Basically, you can use it however you like, but I would
 *  appreciate some credit when you do.
 *
 * Disclaimer:
 *  I make no guarantees that this code won't make your server explode in a
 *  shower of blue flames. However, I don't expect that it will. I am actually
 *  confident that this code will be helpful.
 *
 *  That said, this is still my beta release. If you find any bugs, please
 *  let me know so I can fix them.
 *
 * - Ammon Lauritzen [Apr 21, '08]
 *   http://ammonlauritzen.com/blog/2008/04/22/flash-policy-service-daemon/
 *
 * Changelog
 * 0.9.c
 *   - Fixed some typoes that were the result of how I combined everything
 *     into a single file. Thanks Alex!
 * 0.9.b
 *   - Original version to be posted at this url.
 * 0.9.a
 *   - Original proof of concept code posted online.
 */

/*** Config ***/

// where should we save the log output?
$log_filename = &quot;/tmp/flash-policy.log&quot;;

// uncomment these lines to choose which user to run the daemon as
// default behavior is to look up and attempt to run as 'nobody'
# $daemon_uid = 99;
# $daemon_gid = 99;

// set this if you want to use an external file in stead of the default
$xml_filename = &quot;&quot;;
$default_xml =
	'&lt;'.'?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?'.'&gt;'.
    '&lt;cross-domain-policy xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:noNamespaceSchemaLocation=&quot;http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd&quot;&gt;'.
        '&lt;allow-access-from domain=&quot;*&quot; to-ports=&quot;*&quot; secure=&quot;false&quot; /&gt;'.
        '&lt;site-control permitted-cross-domain-policies=&quot;all&quot; /&gt;'.
    '&lt;/cross-domain-policy&gt;';

/*** You shouldn't have to edit anything below here ***/

/////////////////////////////////////////////////////////////////////////////
class Logger {
	public function __construct( $logfile ) {
        $this-&gt;logfile = $logfile;
        $this-&gt;open_log();
    }// end: constructor

    public function __destruct() {
        @fflush( $this-&gt;fh );
        @fclose( $this-&gt;fh );
    }// end: destructor

    public function log( $msg ) {
		// deal with redundant log spam
		if( $msg == $this-&gt;last_msg ) {
			$this-&gt;last_msg_count++;
			return;
		} else if( $this-&gt;last_msg_count ) {
			$this-&gt;write( &quot;Last message repeated &quot;.$this-&gt;last_msg_count.&quot; times.&quot; );
		}
		$this-&gt;lasg_msg = $msg;
		$this-&gt;last_msg_count = 0;

		// actually write the log message out
		$this-&gt;write( $msg );
	}// end: log

	private function write( $msg ) {
        $msg = sprintf(&quot;[%s] %s\\n&quot;,date(&quot;y-m-d H:i:s&quot;),$msg);
        $succ = @fwrite( $this-&gt;fh, $msg );
        if( $succ === FALSE ) {
            echo $msg;
        }
    }// end: write

	private function open_log() {
        if( !file_exists($this-&gt;logfile) ) {
            touch( $this-&gt;logfile );
            chmod( $this-&gt;logfile, 0664 );
        }
        $this-&gt;fh = @fopen( $this-&gt;logfile, &quot;a&quot; );
    }
}// end: logger class

/////////////////////////////////////////////////////////////////////////////
class Daemon {
	public function __construct() {
		error_reporting(0);
		set_time_limit(0);

		global $log_filename, $pid_filename;
		$this-&gt;logger = new Logger( $log_filename );
		$this-&gt;pid_filename = $pid_filename;

		$this-&gt;log_tag = $this-&gt;daemon_tag = &quot;launcher&quot;;
		$this-&gt;debug( &quot;constructed&quot;, $this-&gt;daemon_tag );
	}// end: constructor

	public function __destruct() {
		$this-&gt;debug( &quot;destructing&quot;, $this-&gt;daemon_tag );
	}// end: destructor

	protected function log( $msg, $tag = false ) {
		if( $tag == false )
			$tag = $this-&gt;log_tag;
		$this-&gt;logger-&gt;log( $tag.&quot;: &quot;.$msg );
	}// end: log
	protected function debug( $msg, $tag = false ) {
		if( DEBUG )
			$this-&gt;log( $msg, $tag );
	}// end: debug

	protected function main() {
		$this-&gt;log( &quot;override main() in daemon subclass&quot;, $this-&gt;daemon_tag );
		$this-&gt;stop();
	}// end: main

	public function start() {
		$this-&gt;log( &quot;starting daemon&quot;, $this-&gt;daemon_tag );
		if( !$this-&gt;_start() ) {
			$this-&gt;log( &quot;unable to start daemon&quot;, $this-&gt;daemon_tag );
			return;
		}

		// report execution details
		$this-&gt;log( &quot;uid = &quot;.posix_getuid().&quot;, gid = &quot;.posix_getgid() );
		$this-&gt;log( &quot;cwd = &quot;.getcwd() );

		// invoke main loop
		$this-&gt;running = true;
		while( $this-&gt;running ) {
			$this-&gt;main();
		}
	}// end: start
	private function _start() {
		if( !$this-&gt;_fork() ) {
			return false;
		}// end: try to fork

		if( !posix_setsid() ) {
			$this-&gt;log( &quot;unable to setsid()&quot;, $this-&gt;daemon_tag );
			return false;
		}// end: try to set sid

		if( !$this-&gt;_suid() ) {
			return false;
		}// end: try to set uid

		// register signal handler
		declare(ticks = 1);
		pcntl_signal( SIGTERM, array(&amp;$this, &quot;on_sigterm&quot;) );
		// chdir somewhere moderately safe by default
		chdir( '/tmp' );
		return true;
	}// end: _start
	private function _fork() {
		$this-&gt;log( &quot;forking...&quot;, $this-&gt;daemon_tag );
		$pid = pcntl_fork();
		if( $pid == -1 ) {
			// error
			$this-&gt;log( &quot;unable to fork&quot;, $this-&gt;daemon_tag );
			return false;
		} else if( $pid ) {
			// parent
			$this-&gt;debug( &quot;done with parent&quot;, $this-&gt;daemon_tag );
			exit( 0 );
		} else {
			// child
			$this-&gt;daemon_tag = &quot;daemon&quot;;
			$this-&gt;child = true;
			$this-&gt;pid = posix_getpid();
			$this-&gt;debug( &quot;child pid = &quot;.$this-&gt;pid );
			return true;
		}
	}// end: _fork
	private function _suid() {
		global $daemon_uid, $daemon_gid;
		if( !isset($daemon_uid) || !isset($daemon_gid) ) {
			// we didn't get a uid/gid, try to make one up
			$this-&gt;debug( &quot;searching for info on 'nobody'&quot;, $this-&gt;daemon_tag );
			$res = posix_getpwnam(&quot;nobody&quot;);
			if( $res !== FALSE ) {
				$uid = $res['uid'];
				$gid = $res['gid'];
			} else {
				// the 'nobody' user doesn't exist on this system, refuse
				// to daemonize as root
				$this-&gt;log( &quot;unable to find info on 'nobody' user&quot;, $this-&gt;daemon_tag );
				return false;
			}
		} else {
			$this-&gt;debug( &quot;got uid/gid of $daemon_uid/$daemon_gid&quot;, $this-&gt;daemon_tag );
			$uid = $daemon_uid;
			$gid = $daemon_gid;
		}
		// actually try to switch now
		if( !posix_setgid($gid) ) {
			$this-&gt;log( &quot;unable to set gid to &quot;.$gid, $this-&gt;daemon_tag );
			return false;
		} else if( !posix_setuid($uid) ) {
			$this-&gt;log( &quot;unable to set uid to &quot;.$uid, $this-&gt;daemon_tag );
			return false;
		} else {
			return true;
		}
	}// end: _suid

	public function stop() {
		$this-&gt;log( &quot;stopping daemon&quot;, $this-&gt;daemon_tag );
		$this-&gt;running = false;
	}// end: stop

	protected function on_sigterm( $sig ) {
		if( $sig == SIGTERM ) {
			$this-&gt;log( &quot;got SIGTERM&quot;, $this-&gt;daemon_tag );
			$this-&gt;stop();
			exit( 0 );
		}
	}// end: on_sigterm
}// end: daemon class

/////////////////////////////////////////////////////////////////////////////
class FlashPolicyService extends Daemon {
	public function __construct() {
		parent::__construct();
		$this-&gt;log_tag = &quot;fps&quot;;
		$this-&gt;debug( &quot;constructing&quot; );

		$this-&gt;connections = array();
		$this-&gt;request_str = &quot;&lt;policy-file-request/&gt;&quot;;
		$this-&gt;port = 843;

		// get our xml
		global $xml_filename, $default_xml;
		if( strlen($xml_filename) == 0 || !file_exists($xml_filename) ) {
			$this-&gt;log( &quot;unable to read xml from '$xml_filename', using default&quot; );
			$this-&gt;xml = $default_xml;
		} else {
			$this-&gt;log( &quot;reading policy xml from $xml_filename&quot; );
			$this-&gt;xml = file_get_contents( $xml_filename );
		}
		$this-&gt;debug( &quot;policy xml: &quot;.$this-&gt;xml );
		// make sure we're null terminated
		$this-&gt;xml = trim($this-&gt;xml).&quot;\\n\\0&quot;;

		// and get going
		$this-&gt;init();
	}// end: constructor

	public function __destruct() {
		parent::__destruct();
		if( $this-&gt;sock )
			@fclose($this-&gt;sock);
	}// end: destructor

	private function init() {
		$this-&gt;debug( &quot;init...&quot; );
		if( $this-&gt;check_socket() ) {
			parent::start();
		} else {
			$this-&gt;log( &quot;not starting daemon&quot; );
		}
	}// end: init

	protected function main() {
		if( $this-&gt;sock ) {
			while( true ) {
				$this-&gt;accept_socket();
			}
		} else {
			$this-&gt;log( &quot;server socket is closed?!&quot; );
			parent::stop();
		}
		// paranoia to keep from absolutely hosing cpu if something goes wrong
		usleep( 10000 );	// 10ms
	}// end: main

	private function check_socket() {
		$this-&gt;sock = @socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
		if( !$this-&gt;sock ) {
			$this-&gt;log( &quot;unable to create socket&quot; );
		} else {
			$succ = @socket_bind( $this-&gt;sock, &quot;0.0.0.0&quot;, $this-&gt;port );
			if( !$succ ) {
				$this-&gt;log( &quot;unable to bind to port &quot;.$this-&gt;port );
			} else {
				$backlog = 100;
				$succ = @socket_listen( $this-&gt;sock, $backlog );
				if( !$succ ) {
					$this-&gt;log( &quot;unable to listen with backlog of $backlog&quot; );
				} else {
					// everything's good
					return true;
				}
			}// end: able to bind
		}// end: able to create socket

		// if we got here, it's an error. abort.
		$errno = socket_last_error( $this-&gt;sock );
		$errstr = socket_strerror( $errno );
		$this-&gt;log( &quot;socket error: $errno: $errstr&quot; );
		return false;
	}// end: check_socket

	private function accept_socket() {
		$r_socks = array_merge( array($this-&gt;sock), $this-&gt;connections );
		$this-&gt;debug( &quot;selecting on &quot;.count($r_socks).&quot; sockets&quot; );

		// block until something interesting happens
		$select = @socket_select( $r_socks, $w_socks = NULL, $e_socks = NULL, NULL );
		if( !$select )
			return;

		// did we get a new connection?
		if( in_array($this-&gt;sock, $r_socks) ) {
			$conn = @socket_accept( $this-&gt;sock );
			if( $conn !== false )
				@socket_getpeername( $conn, $addr );
			$this-&gt;log( &quot;connection accepted from $addr, $conn&quot; );
			$this-&gt;connections[] = $conn;
		}// end: got a new connection

		// check for policy requests
		foreach( $r_socks as $conn ) {
			// ignore the server socket
			if( $conn == $this-&gt;sock )
				continue;

			// read from the client
			$data = @socket_read( $conn, 1024 );
			if( $data === FALSE ) {
				$this-&gt;log( &quot;got disconnect from $conn&quot; );
			}// end: client closed connection
			else {
				$this-&gt;debug( &quot;read '&quot;.trim($data).&quot;' from $conn&quot; );
				if( strpos($data, $this-&gt;request_str) !== FALSE ) {
					$this-&gt;log( &quot;sending policy xml to $conn&quot; );
					@socket_write( $conn, $this-&gt;xml );
				} else {
					$this-&gt;log( &quot;got invalid request from $conn&quot; );
				}
			}// end: got data from the client

			// and always disconnect after having read something, whether
			// it was a valid request or not - especially if it wasn't <img src='http://ammonlauritzen.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />
			@socket_close( $conn );
			$key = array_search( $conn, $this-&gt;connections );
			if( $key !== FALSE )
				unset( $this-&gt;connections[$key] );
		}// end: foreach socket
	}// end: accept_socket

}// end: flash policy service class

/////////////////////////////////////////////////////////////////////////////
/**
 * Actual execution code here. This checks if the php install has all of our
 * requisite extensions, makes sure we're launching as root, and checks if
 * debug mode was requested before actually starting the daemon.
 */

// make sure we have required extensions
$required_extensions = array( &quot;sockets&quot;, &quot;posix&quot;, &quot;pcntl&quot; );
$missing_extension = false;
foreach( $required_extensions as $ext ) {
	if( !extension_loaded($ext) ) {
		echo &quot;Missing required php extension '$ext'.\n&quot;;
		$missing_extension = true;
	}
}
if( $missing_extension ) {
	exit( 1 );
}

// make sure we launch as root, otherwise we can't bind to 843
if( posix_getuid() != 0 || posix_getgid() != 0 ) {
	echo &quot;Policy service must be started as root.\n&quot;;
	exit( 1 );
}

// see if we're in debug mode
define( &quot;DEBUG&quot;, in_array('-d',$argv) );

// start the daemon
$fps = new FlashPolicyService();
?&gt;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://ammonlauritzen.com/blog/2008/04/22/flash-policy-service-daemon/feed/</wfw:commentRss>
		<slash:comments>43</slash:comments>
		</item>
	</channel>
</rss>
