newspaint

Documenting Problems That Were Difficult To Find The Answer To

Installing OpenRA on Xubuntu 14.04

I had to run the following command after downloading the OpenRA deb file:

sudo apt-get install libsdl2-2.0-0 mono-runtime mono-runtime-sgen liblua5.1-0 mono-runtime-common mono-gac mono-4.0-gac libmono-corlib4.5-cil libmono-security4.0-cil cli-common libmono-system4.0-cil libmono-system-core4.0-cil libmono-system-drawing4.0-cil libmono-system-data4.0-cil libmono-system-numerics4.0-cil libmono-system-runtime-serialization4.0-cil libmono-system-xml-linq4.0-cil libmono-posix4.0-cil libmono-data-tds4.0-cil libmono-system-configuration4.0-cil libmono-system-enterpriseservices4.0-cil libmono-system-transactions4.0-cil libmono-system-xml4.0-cil libgdiplus libmono-system-security4.0-cil libmono-i18n4.0-all

Want to Slow Linux ZFS Flushing/Syncing to Disk Every 5 Seconds?

So you’ve got ZFS on Linux and you notice your hard drive LED light up every 5 seconds. How can you slow that down?

The zfs_txg_timeout parameter is described as:

~# modinfo zfs |grep zfs_txg_timeout
parm:           zfs_txg_timeout:Max seconds worth of delta per txg (int)

You can check the current value of this parameter:

~# cat /sys/module/zfs/parameters/zfs_txg_timeout
5

To verify this is the parameter you want to change, set this to 10 and count the number of seconds between the hard drive light coming on:

~# echo 10 >/sys/module/zfs/parameters/zfs_txg_timeout

Once you know this is the parameter causing the hard drive activity you can tell the ZFS module the default you want it to start with on next boot by editing /etc/modprobe.d/zfs.conf and adding a line similar to the following:

options zfs zfs_txg_timeout=30

Take care when changing this number as it may result in a higher probability of lost data in the event of a power outage.

Thunar and Ristretto on Xubuntu 14.04 Do Not Render Thumbnails

I was puzzled and searching all over the Internet for an explanation why the file explorer (Thunar) and the image viewer (Ristretto) were not displaying thumbnails for JPEG files. I know they had rendered them before. I tried all sorts of things, deleting local cache (~/.thumbnails/* and ~/.cache/thumbnails/*), ensuring various packages had been installed.

Finally I stumbled across this forum post which gave the necessary clue:

A package called tumbler is needed for thunar to automatically generate thumbnails.

I checked to see that I had tumbler:

~$ dpkg -l |grep tumbler
ii  libtumbler-1-0  0.1.30-1ubuntu1  amd64  library for tumbler, a D-Bus thumbnailing service
ii  tumbler         0.1.30-1ubuntu1  amd64  D-Bus thumbnailing service
ii  tumbler-common  0.1.30-1ubuntu1  all    D-Bus thumbnailing service (common files)

Then I checked to see if a tumbler process was running, and proceeded to kill it:

~$ ps fax |grep tumbler
21322 ?        SNl    0:01          \_ /usr/lib/x86_64-linux-gnu/tumbler-1/tumblerd
~$ kill 21322
~$ ps fax |grep tumbler
~$

or…

~$ killall tumblerd

Now when I opened a thunar file explorer the thumbnails were being rendered, and also rendered in ristretto, too.

Past Duration 0.999992 Too Large

While encoding a video in ffmpeg I encountered the message:

Past duration 0.999992 too large
Last message repeated 11 times

The problem magically disappeared when I changed audio codec and bitrate for the output stream.

My input stream reported itself as:

Stream #0:2: Audio: ac3, 48000 Hz, 5.1(side), 448 kb/s

But the settings I used to encode the stream to give me the error were:

-c:a:1 aac -b:a:1 160k -metadata:s:a:1 title="5.1 channel"

Note that the input stream was ac3 but I was converting it to aac on the output. Not necessarily a bad thing for stereo output, but for 5.1 maybe not the best target codec. The ffmpeg wiki high quality audio guide states the ac3 codec is for Dolby Digital.

When I changed to the following settings the error message disappeared:

-c:a:1 ac3 -b:a:1 320k -metadata:s:a:1 title="5.1 channel"

Ripping A Video From DVD Using FFMPEG

I have a collection of DVDs I need to put in storage to clear up some space. This page lists what I have learned about how to take DVD video and create a video file as a result.

There are many other articles on the subject, such as this one.

A DVD usually has two directories on it, AUDIO_TS and VIDEO_TS. For DVD videos we only care about the VIDEO_TS directory.

Inside the VIDEO_TS directory there are a collection of files, e.g.:

     16384 Aug 23  2005 VIDEO_TS.BUP
     16384 Aug 23  2005 VIDEO_TS.IFO
    157696 Aug 23  2005 VIDEO_TS.VOB
     30720 Aug 23  2005 VTS_01_0.BUP
     30720 Aug 23  2005 VTS_01_0.IFO
 176521216 Aug 23  2005 VTS_01_0.VOB
  22235136 Aug 23  2005 VTS_01_1.VOB
     43008 Aug 23  2005 VTS_02_0.BUP
     43008 Aug 23  2005 VTS_02_0.IFO
    157696 Aug 23  2005 VTS_02_0.VOB
1073565696 Aug 23  2005 VTS_02_1.VOB
1073565696 Aug 23  2005 VTS_02_2.VOB
 839757824 Aug 23  2005 VTS_02_3.VOB

We’re only interested in *.VOB files, and generally anything after VTS_02_* as VTS_01_* is usually the contents page for the DVD.

So, we want to make a video out of the VTS_02_*.VOB files. Before we can proceed any further it is necessary for us to make a single, continuous .mpeg file. Be warned, however, this file will be large (3 gigabytes for 45 minutes).

Thus we want to have an input file that is a concatenation of the VOB files, in order. We use the input file type of concat:file_1:file_2:

Again, before we proceed further, we need to find out what streams are in the source video:

~$ ffmpeg -i "concat:VTS_02_0.VOB|VTS_02_1.VOB|VTS_02_2.VOB|VTS_02_3.VOB" -c copy /dev/null
Input #0, mpeg, from 'concat:VTS_02_0.VOB|VTS_02_1.VOB|VTS_02_2.VOB|VTS_02_3.VOB':
  Duration: 00:45:08.97, start: 0.280000, bitrate: 8821 kb/s
    Stream #0:0[0x1bf]: Data: dvd_nav_packet
    Stream #0:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv), 720x576 [SAR 16:15 DAR 4:3], 8000 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc
    Stream #0:2[0x20]: Subtitle: dvd_subtitle
    Stream #0:3[0x81]: Audio: ac3, 48000 Hz, 5.1(side), fltp, 448 kb/s
    Stream #0:4[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 224 kb/s

Now, when we create our intermediate (temporary) .mpeg file, we need to specify which streams we want to extract. By default ffmpeg only extracts the best video and best audio stream it can find. If we want to keep all our audio streams then we have to explicitly tell ffmpeg this using the -map directive, one per stream we want to keep, in order.

In this case I want to keep streams 0:1 (video), 0:3 (5.1 audio), and 0:4 (audio stereo). But actually I want the stereo track to come before the 5.1 audio, so I’ll use the following command to make my intermediate file:

~$ ffmpeg -i "concat:VTS_02_0.VOB|VTS_02_1.VOB|VTS_02_2.VOB|VTS_02_3.VOB" -map 0:1 -map 0:4 -map 0:3 -f mpeg -c copy /tmp/intermediate.mpeg
Input #0, mpeg, from 'concat:VTS_02_0.VOB|VTS_02_1.VOB|VTS_02_2.VOB|VTS_02_3.VOB':
  Duration: 00:45:08.97, start: 0.280000, bitrate: 8821 kb/s
    Stream #0:0[0x1bf]: Data: dvd_nav_packet
    Stream #0:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv), 720x576 [SAR 16:15 DAR 4:3], 8000 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc
    Stream #0:2[0x20]: Subtitle: dvd_subtitle
    Stream #0:3[0x81]: Audio: ac3, 48000 Hz, 5.1(side), fltp, 448 kb/s
    Stream #0:4[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 224 kb/s
Output #0, mpeg, to '/tmp/intermediate.mpeg':
  Metadata:
    encoder         : Lavf57.15.100
    Stream #0:0: Video: mpeg2video, yuv420p, 720x576 [SAR 16:15 DAR 4:3], q=2-31, 8000 kb/s, 25 fps, 25 tbr, 90k tbn, 25 tbc
    Stream #0:1: Audio: ac3, 48000 Hz, stereo, 224 kb/s
    Stream #0:2: Audio: ac3, 48000 Hz, 5.1(side), 448 kb/s
Stream mapping:
  Stream #0:1 -> #0:0 (copy)
  Stream #0:4 -> #0:1 (copy)
  Stream #0:3 -> #0:2 (copy)
video:2645654kB audio:222221kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.389088%

So now we have an intermediate file. It is from this we can create the desired output.

You might have an encrypted region-specific DVD. In this case the ffmpeg tool will not work by itself. A tool that will read encrypted DVDs is a port of ffmpeg called vgtmpeg. There are Windows and Linux builds available. This uses the same command line except for the input you specify an input of something like:

~$ vgtmpeg -i dvd:///media/user/DVD_VIDEO/?title=3
C:\> vgtmpeg.exe -i dvd://F:\title=3

Note that region-coded DVDs will not work in a DVD drive that has no region code set (some new drives have no code initially configured). In Linux you can set a drive’s region code (a maximum of 5 times) using the command-line tool regionset. Once the drive’s region has been set, however, the vgtmpeg tool should work with a drive from any region even if it is different from the drive’s specified region.

Now you can compress the intermediate file.

You will probably want to tinker with the settings to suit your preferences.

First you have to specify which streams you want to encode, again. You may be forced to specify the display aspect ratio. And you may wish to add metadata (descriptions) to the audio channels (and subtitles if you use them).

Here are some settings for an example:

~$ ffmpeg -i /tmp/intermediate.mpeg \
  -map 0:0 -map 0:1 -map 0:2 \
  -c:v libx264 -preset medium -b:v 600k -aspect 16:9 -strict -2 -g 50 -vf yadif \
  -c:a:0 aac -b:a:0 160k -metadata:s:a:0 title=\"2 channel\" \
  -c:a:1 ac3 -b:a:1 320k -metadata:s:a:1 title=\"5.1 channel\" \
  /tmp/final.mkv

The -map directives select what streams we want from our input file.

The -c:v libx264 directive selects the video codec. The -preset option chooses an H.264 encoding speed to compression ratio. The -b:v option specifies a constant output bitrate (you can use two-pass encoding if you wish to utilise a different strategy). The -aspect 16:9 may not be necessary but forces the default display by the client reading the file later. The -strict -2 options are required for H.264 encoding. The -g 50 option makes the encoder store a whole image frame every 50 frames (useful for being able to forward or reverse into the video). The -vf yadif video filter de-interlaces the source video.

The -c:a:0 option sets the audio codec on the first audio stream (which is different from the stream number assigned). The -b:a:0 option sets the bit rate for the first audio stream. And the -metadata:s:a:0 option sets a title for the stream, which can make it easier for those watching your video to choose an appropriate named audio track rather than guessing what the difference is.

Finally the output file name is specified.

Audio

A guide to high quality audio with ffmpeg is documented on the ffmpeg wiki.

A 5.1 stream is perhaps better encoded using the “ac3” (Dolby Digital) codec.

If you want to convert a 5.1 stream to stereo you can use the following to select a particular audio stream and apply a filter to it:

-af:a:0 'pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR'

Subtitles

DVD subtitles may not appear in the first VOB file in a set; it may be necessary to tell ffmpeg to scan ahead deep into the file so that it can find the subtitles in the second VOB file. The directives to do this are -probesize and -analyzeduration – the first being the number of bytes to read ahead into the file, and the second being the number of microseconds (of which there are 1,000,000 per second). These directives must come before the input file directive.

So, to scan ahead 4GB and 1 hour, the an example command may be:

~$ ffmpeg -i -probesize 4G -analyzeduration 3600M "concat:VTS_02_0.VOB|VTS_02_1.VOB|VTS_02_2.VOB|VTS_02_3.VOB" -c copy /dev/null

You can specify a title to go along with a subtitle track, such as:

-metadata:s:s:0 title="English"

Deluge BitTorrent Client Refusing To Set File Priority

The Deluge BitTorrent client will sometimes appear to completely ignore an instruction to change file priority in a torrent. Perhaps you want to set a file to “Do Not Download” yet Deluge won’t allow you to, it will refuse the request silently.

You find yourself in this situation, you right click on a file, and choose a different priority, such as “Do Not Download” as in the following screenshot of the webui:

Attempting to change file priority in Deluge

Attempting to change file priority in Deluge

Nothing happens. Why not? Well when you right-click on the file and set a priority a JSON request is made to the WebUI with the following payload:

{"method":"core.set_torrent_file_priorities","params":["406ce341e375e984527ddc6dee24cbf7a7aff032",[1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],"id":71}

This call is found to be translated in /usr/lib/python2.7/dist-packages/deluge/core/core.py:

    @export
    def set_torrent_file_priorities(self, torrent_id, priorities):
        """Sets a torrents file priorities"""
        return self.torrentmanager[torrent_id].set_file_priorities(priorities)

The corresponding call to set_file_priorities is in /usr/lib/python2.7/dist-packages/deluge/core/torrent.py:

    def set_file_priorities(self, file_priorities):
        if len(file_priorities) != len(self.get_files()):
            log.debug("file_priorities len != num_files")
            self.options["file_priorities"] = self.handle.file_priorities()
            return

        if self.options["compact_allocation"]:
            log.debug("setting file priority with compact allocation does not work!")
            self.options["file_priorities"] = self.handle.file_priorities()
            return

This, then, has the answer. You cannot set file priorities if you set file allocation to “compact” when you added the torrent.

If you set file allocation to compact in Deluge then you cannot set file download priority

If you set file allocation to compact in Deluge then you cannot set file download priority

Solution

If you want to be able to set download priorities (including not downloading) then you must set file allocation to “full” when adding a torrent.

How Could We Harness The Power of Lightning?

It’s important to blog this because I’ve been thinking about this problem for some time and in an effort to prevent others from claiming an idea as their own I’m putting my own ideas here.

Electrolysis of Water

Electricity can be used to split water into hydrogen and oxygen gases using electrolysis. Well, pure water. For saltwater (brine) the products are chlorine and hydrogen.

If hydrogen could be collected it could be cleanly burned in a controlled manner to produce heat and electricity.

How could this possibly work? Perhaps have a grid of floating platforms out at sea with inverted cannisters underneath initially filled with seawater; half the cannisters would have electrical nodes in the cannisters that were “grounded” via a large external metallic grid, the other half would have electrical nodes in the cannister attached to a large tall metallic tower or antenna.

The theory being that electricity striking the antenna would quickly and near instantly force mass electrolysis to occur filling one set of containers of hydrogen gas.

Electrolysis of Seawater by Lightning

Electrolysis of Seawater by Lightning

Which container would fill with hydrogen gas? The answer is “it depends”. Most cloud to surface lightning is a negative strike meaning a net flow of electrons are transferred from cloud to ground. That would mean the electrons flow into the cannister that is attached to the lightning rod/tower and that cannister would produce the hydrogen.

However occasionally (5% of strikes) a far more powerful positively charged strike occurs which would mean the hydrogen would be collected in the grounded cannister instead.

If this apparatus was hit by both negative and positive lightning a cannister could potentially fill with hydrogen and chlorine. If they combined they would produce HCl, or hydrochloric acid – a dangerous substance to humans.

So this proposal is not necessarily a good one. But it is, nonetheless, mine, and nobody may patent it. I thought of this idea at least ten years ago if not more.

Heat Transfer

Actual current flow, while substantial, is incredibly brief during a lightning strike. But it always leaves burn marks wherever it goes. The substances it flows through become incredibly hot – and air itself becomes so hot it expands faster than the speed of sound, creating a shockwave that forms the sound of thunder.

Perhaps that heat can be captured and stored for electricity generation.

Disabling the Alarm/Beep on APC UPS Back-UPS 1400 on Ubuntu 14.04

Note this was also done on a APC UPS Back-UPS 700 as well as a APC UPS Back-UPS 1400.

So you don’t want to be interrupted by long piercing beeps from your APC-branded UPS every minute when the power goes out. That’s fair. Perhaps it is night time and the power goes out occasionally. You just want your server to keep humming as long as it can. If the power stays off for two hours straight, then fine, it can die, but if the power recovers in that time you just don’t want to know about it. You bought your un-interruptable power supply (UPS) to increase reliability in, perhaps, an area with an unreliable electricity supply.

Note that for some people the alarm is important to them: they must take action in the event of a power outage. This guide is not for you.

If you have Ubuntu Linux 14.04 Trusty Tahr then it is actually very easy to disable the alarm on your UPS.

Attach USB cable from computer to UPS

Attach USB cable from computer to UPS

First connect your UPC to your PC (or laptop) via USB cable.

Connect the USB cable to the back of the UPS

Connect the USB cable to the back of the UPS

Confirm that it is found by running lsusb:

user@host:~$ lsusb
Bus 003 Device 013: ID 051d:0002 American Power Conversion Uninterruptible Power Supply

Next install apcupsd via apt-get:

user@host:~$ sudo apt-get install apcupsd
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  apcupsd-doc
Suggested packages:
  apcupsd-cgi
The following NEW packages will be installed:
  apcupsd apcupsd-doc
0 upgraded, 2 newly installed, 0 to remove and 12 not upgraded.
Need to get 891 kB of archives.
After this operation, 2,378 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://www.mirrorservice.org/sites/archive.ubuntu.com/ubuntu/ trusty/universe apcupsd-doc all 3.14.10-2build1 [608 kB]
Get:2 http://www.mirrorservice.org/sites/archive.ubuntu.com/ubuntu/ trusty/universe apcupsd amd64 3.14.10-2build1 [282 kB]
Fetched 891 kB in 0s (4,839 kB/s)
Selecting previously unselected package apcupsd-doc.
(Reading database ... 378817 files and directories currently installed.)
Preparing to unpack .../apcupsd-doc_3.14.10-2build1_all.deb ...
Unpacking apcupsd-doc (3.14.10-2build1) ...
Selecting previously unselected package apcupsd.
Preparing to unpack .../apcupsd_3.14.10-2build1_amd64.deb ...
Unpacking apcupsd (3.14.10-2build1) ...
Processing triggers for doc-base (0.10.5) ...
Processing 1 added doc-base file...
Processing triggers for man-db (2.6.7.1-1) ...
Processing triggers for ureadahead (0.100.0-16) ...
Setting up apcupsd-doc (3.14.10-2build1) ...
Setting up apcupsd (3.14.10-2build1) ...
update-rc.d: warning:  start runlevel arguments (1 2 3 4 5) do not match apcupsd Default-Start values (2 3 4 5)
update-rc.d: warning:  stop runlevel arguments (0 6) do not match apcupsd Default-Stop values (0 1 6)
Please check your configuration ISCONFIGURED in /etc/default/apcupsd
Processing triggers for ureadahead (0.100.0-16) ...
user@host:~$ 

Now you’ll have to sudo vi /etc/apcupsd/apcupsd.conf and comment out the following lines and replace them with the following:

#UPSCABLE smart
UPSCABLE usb

#UPSTYPE apcsmart
#DEVICE /dev/ttyS0
UPSTYPE usb

Now you’re ready to run apctest which is documented on the apcupsd page:

user@host:~$ sudo apctest
2016-07-16 18:48:25 apctest 3.14.10 (13 September 2011) debian
Checking configuration ...
Attached to driver: usb
sharenet.type = Network & ShareUPS Disabled
cable.type = USB Cable
mode.type = USB UPS Driver
Setting up the port ...
Doing prep_device() ...

You are using a USB cable type, so I'm entering USB test mode
Hello, this is the apcupsd Cable Test program.
This part of apctest is for testing USB UPSes.

Getting UPS capabilities...SUCCESS

Please select the function you want to perform.

1)  Test kill UPS power
2)  Perform self-test
3)  Read last self-test result
4)  View/Change battery date
5)  View manufacturing date
6)  View/Change alarm behavior
7)  View/Change sensitivity
8)  View/Change low transfer voltage
9)  View/Change high transfer voltage
10) Perform battery calibration
11) Test alarm
12) View/Change self-test interval
 Q) Quit

Select function number: 6

Current alarm setting: ENABLED
Press...
 E to Enable alarms
 D to Disable alarms
 Q to Quit with no changes
Your choice: Select function: D

New alarm setting: DISABLED

1)  Test kill UPS power
2)  Perform self-test
3)  Read last self-test result
4)  View/Change battery date
5)  View manufacturing date
6)  View/Change alarm behavior
7)  View/Change sensitivity
8)  View/Change low transfer voltage
9)  View/Change high transfer voltage
10) Perform battery calibration
11) Test alarm
12) View/Change self-test interval
 Q) Quit

Select function number: Q

user@host:~$

Fairly straightforward.

Note you may want to uninstall the apcupsd package from your system now.

user@host:~$ sudo apt-get remove apcupsd
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  apcupsd-doc
Use 'apt-get autoremove' to remove them.
The following packages will be REMOVED:
  apcupsd
0 upgraded, 0 newly installed, 1 to remove and 12 not upgraded.
After this operation, 749 kB disk space will be freed.
Do you want to continue? [Y/n] y
(Reading database ... 379108 files and directories currently installed.)
Removing apcupsd (3.14.10-2build1) ...
Please check your configuration ISCONFIGURED in /etc/default/apcupsd
Please check your configuration ISCONFIGURED in /etc/default/apcupsd
Processing triggers for ureadahead (0.100.0-16) ...
Processing triggers for man-db (2.6.7.1-1) ...
user@host:~$

Implementing Custom Sort For BTree In Rust

So you want to use a std::collections::btree_map::BTreeMap but you want your keys sorted in a different way to the default for the type.

In this example I show how you can provide a BTreeMap that has String keys but sorted in reverse order. But, by altering the cmp() function for the std::cmp::Ord trait for a custom type, you can make the sort in any order you desire.

It is a bit of a pain, however. As this post points out you have to define partial_cmp() and partial_eq() functions from other traits as well, as well as adding the Eq trait to your struct.

// http://rustbyexample.com/custom_types/structs.html
struct RString {
    s: String,
}

use std::cmp::Ord;
use std::cmp::Ordering;

impl PartialEq for RString {
    fn eq(&self, other:&Self) -> bool {
        if self.s == other.s {
            true
        } else {
            false
        }
    }
}

// this does not actually have any methods, it's just a flag on the type
impl Eq for RString { }

// make partial_cmp() just return result from cmp()
impl PartialOrd for RString {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        let me: &String = &(self.s);
        let them: &String = &(other.s);

        Some( me.cmp( them ) )
    }
}

impl Ord for RString {
    fn cmp(&self, other:&Self) -> Ordering {
        let me: &String = &(self.s);
        let them: &String = &(other.s);

        if me > them {
            Ordering::Less
        } else if me < them {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

use std::env;
use std::collections::btree_map::BTreeMap;
fn main() {
    // collect environment variable keys into a vector we can sort
    let mut sortedmap : BTreeMap<Box<RString>,Box<String>> = BTreeMap::new();

    for (key, value) in env::vars() {
        sortedmap.insert(
            Box::<RString>::new( RString { s: key } ),
            Box::<String>::new( value )
        );
    }

    for (key, value) in sortedmap {
        println!( "{} => {}", (*key).s, *value );
    }
}

This could also be implemented using a tuple containing a single element:

// http://rustbyexample.com/custom_types/structs.html
struct RString(String);

use std::cmp::Ord;
use std::cmp::Ordering;

impl PartialEq for RString {
    fn eq(&self, other:&Self) -> bool {
        if self.0 == other.0 {
            true
        } else {
            false
        }
    }
}

// this does not actually have any methods, it's just a flag on the type
impl Eq for RString { }

// make partial_cmp() just return result from cmp()
impl PartialOrd for RString {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        let me: &String = &(self.0);
        let them: &String = &(other.0);

        Some( me.cmp( them ) )
    }
}

impl Ord for RString {
    fn cmp(&self, other:&Self) -> Ordering {
        let me: &String = &(self.0);
        let them: &String = &(other.0);

        if me > them {
            Ordering::Less
        } else if me < them {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

use std::env;
use std::collections::btree_map::BTreeMap;
fn main() {
    // collect environment variable keys into a vector we can sort
    let mut sortedmap : BTreeMap<Box<RString>,Box<String>> = BTreeMap::new();

    for (key, value) in env::vars() {
        sortedmap.insert(
            Box::<RString>::new( RString(key) ),
            Box::<String>::new( value )
        );
    }

    for (key, value) in sortedmap {
        println!( "{} => {}", (*key).0, *value );
    }
}

Note that a tuple’s single element can be accessed through the .0 field.

Creating a Basic CGI Executable Using Rust

I created a new cargo project called “cgitest”:

~$ cargo new cgitest --bin
~$ cd cgitest
~/cgitest$

Then I added the following Rust code into src/main.rs:

// Use the following as a guide:
//   http://httpd.apache.org/docs/current/howto/cgi.html

use std::io::Write;
use std::env;
use std::collections::btree_map::BTreeMap;

fn write_stderr( msg : String ) {
    let mut stderr = std::io::stderr();
    write!(&mut stderr, "{}", msg).unwrap();
}

fn write_stderr_s( msg : &str ) {
    write_stderr( msg.to_string() );
}

fn write_stdout( msg : String ) {
    let mut stdout = std::io::stdout();
    write!(&mut stdout, "{}", msg).unwrap();
}

fn write_stdout_s( msg : &str ) {
    write_stdout( msg.to_string() );
}

fn html_escape( msg : String ) -> String {
    let mut copy : String = String::with_capacity( msg.len() );

    for thechar in msg.chars() {
        if thechar == '&' {
            copy.push_str( "&amp;" );
        } else if thechar == '<' {
            copy.push_str( "&lt;" );
        } else if thechar == '>' {
            copy.push_str( "&gt;" );
        } else if thechar == '\"' {
            copy.push_str( "&quot;" );
        } else {
            copy.push( thechar );
        }
    }

    return copy;
}

fn main() {
    write_stdout_s( "Content-type: text/html\n" );
    write_stdout_s( "\n" );
    write_stdout_s( "<html>\n" );
    write_stdout_s( "  <head>\n" );
    write_stdout_s( "    <title>Rust CGI Test</title>\n" );
    write_stdout_s( "    <style type=\"text/css\">\n" );
    write_stdout_s( "      td { border:1px solid black; }\n" );
    write_stdout_s( "      td { font-family:monospace; }\n" );
    write_stdout_s( "      table { border-collapse:collapse; }\n" );
    write_stdout_s( "    </style>\n" );
    write_stdout_s( "  </head>\n" );
    write_stdout_s( "  <body>\n" );
    write_stdout_s( "    <h1>Environment</h1>\n" );
    write_stdout_s( "    <table>\n" );
    write_stdout_s( "      <tr><th>Key</th><th>Value</th></tr>\n" );

    // copy environment into a BTreeMap which is sorted
    let mut sortedmap : BTreeMap<String,String> = BTreeMap::new();
    for (key, value) in env::vars() {
        sortedmap.insert( key, value );
    }

    // output environment into HTML table
    for (key, value) in sortedmap {
        write_stdout(
            format!(
                "      <tr><td>{}</td><td>{}</td></tr>\n",
                html_escape( key ),
                html_escape( value )
            )
        );
    }
    write_stdout_s( "    </table>\n" );
    write_stdout_s( "  </body>\n" );
    write_stdout_s( "</html>\n" );
}

Compiling it using the cargo build --release command produced a binary in target/release/cgitest which can be launched from Apache as a CGI script.

Follow

Get every new post delivered to your Inbox.

Join 29 other followers