diff options
-rwxr-xr-x | maketiles/10kfull | 192 | ||||
-rwxr-xr-x | maketiles/10khalf | 180 | ||||
-rw-r--r-- | maketiles/README | 28 | ||||
-rwxr-xr-x | maketiles/fixindex | 26 | ||||
-rwxr-xr-x | maketiles/packtiles | 157 | ||||
-rw-r--r-- | pnmtilesplit/Makefile | 19 | ||||
-rw-r--r-- | pnmtilesplit/pnmtilesplit.c | 365 | ||||
-rw-r--r-- | tileserver/Makefile | 31 | ||||
-rw-r--r-- | tileserver/base64.c | 272 | ||||
-rw-r--r-- | tileserver/base64.h | 25 | ||||
-rw-r--r-- | tileserver/cdb.c | 476 | ||||
-rw-r--r-- | tileserver/cdb.h | 61 | ||||
-rwxr-xr-x | tileserver/cdbtestrun | 149 | ||||
-rw-r--r-- | tileserver/netstring.c | 44 | ||||
-rw-r--r-- | tileserver/netstring.h | 22 | ||||
-rw-r--r-- | tileserver/tileserver.c | 524 | ||||
-rw-r--r-- | tileserver/tileset.c | 208 | ||||
-rw-r--r-- | tileserver/tileset.h | 32 | ||||
-rw-r--r-- | tileserver/util.c | 53 | ||||
-rw-r--r-- | tileserver/util.h | 36 |
20 files changed, 0 insertions, 2900 deletions
diff --git a/maketiles/10kfull b/maketiles/10kfull deleted file mode 100755 index 873818092..000000000 --- a/maketiles/10kfull +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/perl -w -# -# 10kfull: -# Turn the 1:10,000 Ordnance Survey map tiles into 254-by-254 pixmaps at full -# resolution. -# -# Copyright (c) 2005 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# - -my $rcsid = ''; $rcsid .= '$Id: 10kfull,v 1.5 2006-09-20 15:01:56 chris Exp $'; - -use strict; - -use Digest::SHA1; -use Errno; -use File::stat; -use Geography::NationalGrid; -use IO::Dir; -use IO::File; - -use constant TILE_SIZE_PX => 7874; -use constant TILE_SIZE_M => 5000; - -use constant SUBTILE_SIZE_PX => 254; -use constant SUBTILES_PER_TILE => (TILE_SIZE_PX / SUBTILE_SIZE_PX); - -sub debug (@) { - print STDERR @_; -} - -sub System (@) { - debug("executing command: ", @_, "\n"); - system(@_); - die "command failed with status $?" if ($?); -} - -die "arguments are directories containing input tiles in TIFF format and\n" - . "to which output tiles are to be written, and optional eastings and\n" - . "northings ranges, in the order west, east, south, north\n" - if (@ARGV != 2 && @ARGV != 6); - -my ($inputdir, $outputdir) = @ARGV; -$inputdir =~ s#/$##; -$outputdir =~ s#/$##; -die "$inputdir: not a directory" if (!-d $inputdir); -die "$outputdir: not a directory" if (!-d $outputdir); - -my $inarea = 0; -my ($west, $east, $south, $north); - -if (@ARGV == 6) { - $inarea = 1; - foreach (@ARGV[2 .. 5]) { - die "'$_' is not a valid number\n" if (!/^[1-9]\d*$/); - } - ($west, $east, $south, $north) = @ARGV[2 .. 5]; - die "EAST must be greater than WEST" unless ($east > $west); - die "NORTH must be greater than SOUTH" unless ($north > $south); - - debug("area of interest: west $west, east $east, south $south, north $north\n"); -} - -# -# Within the output directory we create a subdirectory tiles/ containing tile -# images named for their SHA1 checksums, and a text file mapping tile -# coordinates to the SHA1 sums. -# - -die "$outputdir/tiles: $!" - if (!mkdir("$outputdir/tiles", 0755) && !$!{EEXIST}); - -my $index = new IO::File("$outputdir/index", O_WRONLY | O_CREAT | O_TRUNC, 0644) - or die "$outputdir/index: $!"; - -$index->print(<<EOF); -# This index file connects individual tiles of the 1:10,000 Ordnance Survey -# maps at their full resolution (0.635m/px). Each such tile is 254 by 254 -# pixels. The tiles are indexed according to their coordinates within the full -# Ordnance Survey grid, with (0, 0) being the tile at the SW corner of the grid -# (immediately NE of the origin), (1, 0) being the tile to its east, and (0, 1) -# the tile immediately to its north. Note that not all tiles are present. For -# each such tile position this file lists the SHA1 message-digest of the image -# file which represents the tile. -EOF - -my $d = new IO::Dir($inputdir) - or die "$inputdir: $!"; - -my $total_input_size = 0; # KB -my $total_output_size = 0; # KB -while (my $name = $d->read()) { - next unless ($name =~ /\.tiff?$/i); - debug("have image file $inputdir/$name\n"); - - my ($gridsq, $offset, $quadrant) - = map { uc($_) } ($name =~ /^([A-Z]{2})(\d{2})([NS][EW])\./i); - - if (!$gridsq) { - debug(" -> doesn't seem to be a valid 1:10,000 image tile; ignoring\n"); - next; - } - - debug(" -> grid square = $gridsq; offset = $offset; quadrant = $quadrant\n"); - my $p = new Geography::NationalGrid('GB', - GridReference => "$gridsq $offset"); - - my ($E, $N) = ($p->easting(), $p->northing()); - debug(" -> SW corner of grid square is at ($E, $N)\n"); - - $N += TILE_SIZE_M if ($quadrant =~ /^N/); - $E += TILE_SIZE_M if ($quadrant =~ /E$/); - debug(" => SW corner of tile is at ($E, $N)\n"); - - if ($inarea) { - my $tE = $E + TILE_SIZE_M; - my $tW = $E; - my $tN = $N + TILE_SIZE_M; - my $tS = $N; - - if ($tE < $west || $tW > $east - || $tN < $south || $tS > $north) { - debug(" tile is outside area of interest; skipping\n"); - next; - } - } - - my $st = stat("$inputdir/$name"); - my $input_tile_size = $st->size(); - debug(" tile size = $input_tile_size bytes\n"); - $total_input_size += $input_tile_size / 1024.; - - - - # Figure out the tile offsets. - my $x = ($E / TILE_SIZE_M) * SUBTILES_PER_TILE; - my $y = ($N / TILE_SIZE_M) * SUBTILES_PER_TILE; - debug(" => subtile col/row for SW-most subtile is ($x, $y)\n"); - - # Now split this tile into subtiles. - System("tifftopnm $inputdir/$name |" - . " pnmtilesplit -P -f '$outputdir/%d,%d.png'" - . " -p 'pnmtopng -compression 9 2>/dev/null' 254 254"); - - # Consider each of the subtiles. - debug("finding SHA1 digests of tiles and adding them to index...\n"); - my $nduplicates = 0; - my $nnew = 0; - my $size = 0; - for (my $j = 0; $j < SUBTILES_PER_TILE; ++$j) { - debug("\r$j/", SUBTILES_PER_TILE); - my $j2 = SUBTILES_PER_TILE - 1 - $j; - for (my $i = 0; $i < SUBTILES_PER_TILE; ++$i) { - my $fn = "$outputdir/$i,$j2.png"; - my $f = new IO::File($fn, O_RDONLY) - or die "$fn: $!"; - my $sha = new Digest::SHA1(); - $sha->addfile($f); - $sha = $sha->hexdigest(); - $f->close(); - $index->printf("%d %d %s\n", $x + $i, $y + $j, $sha); - my ($n1, $n2, $n3) = ($sha =~ /^(.)(.)(.)/); - mkdir("$outputdir/tiles/$n1"); - mkdir("$outputdir/tiles/$n1/$n2"); - mkdir("$outputdir/tiles/$n1/$n2/$n3"); - my $fn2 = "$outputdir/tiles/$n1/$n2/$n3/$sha.png"; - if (-e $fn2) { - unlink($fn); - ++$nduplicates; - } else { - rename($fn, $fn2) or die "rename: $!"; - ++$nnew; - my $st = stat($fn2); - $size += $st->size(); - } - } - } - debug("\r", SUBTILES_PER_TILE, "/", SUBTILES_PER_TILE, "\n"); - debug("had $nduplicates duplicate tiles + $nnew new, $size bytes\n"); - debug(" output subtiles are ", - sprintf('%.1f%%', 100 * $size / $input_tile_size), - " of input tile size for this tile\n"); - $total_output_size += $size / 1024.; - debug(" so far total input size ", - sprintf('%.1fKB', $total_input_size), "\n"); - debug(" so far total output size ", - sprintf('%.1fKB', $total_output_size), "\n"); - debug(" output subtiles are ", - sprintf('%.1f%%', 100 * $total_output_size / $total_input_size), - " of input tile size so far overall\n"); - $index->flush(); -} diff --git a/maketiles/10khalf b/maketiles/10khalf deleted file mode 100755 index 48d59bc24..000000000 --- a/maketiles/10khalf +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/perl -w -# -# 10khalf: -# Generate half-scale 1:10,000 Ordnance Survey map tiles from the -# full-resolution ones generated by 10kfull. -# -# Copyright (c) 2005 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# - -my $rcsid = ''; $rcsid .= '$Id: 10khalf,v 1.1 2006-09-14 21:43:15 chris Exp $'; - -use strict; - -use Digest::SHA1; -use File::stat; -use IO::File; - -sub debug (@) { - print STDERR @_; -} - -sub System (@) { - #debug("executing command: ", @_, "\n"); - # need bash for <( ) command substitution - system("/bin/bash", '-c', join(' ', @_)); - die "command failed with status $?" if ($?); -} - -die "arguments are directory containing output files from 10kfull and directory to which the half-resolution tiles are to be written" - if (2 != @ARGV); - -my ($inputdir, $outputdir) = @ARGV; -$inputdir =~ s#/$##; -$outputdir =~ s#/$##; -die "$inputdir: not a directory" if (!-d $inputdir); -die "$outputdir: not a directory" if (!-d $outputdir); - -# -# Within the output directory we create a subdirectory tiles/ containing tile -# images named for their SHA1 checksums, and a text file mapping tile -# coordinates to the SHA1 sums. -# - -die "$outputdir/tiles: $!" - if (!mkdir("$outputdir/tiles", 0755) && !$!{EEXIST}); - -my $index = new IO::File("$outputdir/index", O_WRONLY | O_CREAT | O_TRUNC, 0644) - or die "$outputdir/index: $!"; - -$index->print(<<EOF); -# This index file connects individual tiles of the 1:10,000 Ordnance Survey -# maps at half their original resolution (1.27m/px). Each such tile is 254 by -# 254 pixels. The tiles are indexed according to their coordinates within the -# full Ordnance Survey grid, with (0, 0) being the tile at the SW corner of the -# grid (immediately NE of the origin), (1, 0) being the tile to its east, and -# (0, 1) the tile immediately to its north. Note that not all tiles are -# present. For each such tile position this file lists the SHA1 message-digest -# of the image file which represents the tile. -EOF - -my $in_index = new IO::File("$inputdir/index", O_RDONLY) - or die "$inputdir/index: $!"; - -my %fullrestiles = ( ); -my %halfrestiles = ( ); - -my $line; -while (defined($line = $in_index->getline()) && $line =~ /^#/) { } -if (!defined($line)) { - if ($in_index->error()) { - die "$inputdir/index: $! (while reading header)"; - } else { - die "$inputdir/index: premature EOF (while reading header)"; - } -} - -debug("reading $inputdir/index... "); -my $ntiles = 0; -my $nhalfrestiles = 0; -do { - chomp($line); - my ($i, $j, $sha1) = split(/\s+/, $line); - die "$inputdir/index: bad index line '$line'" - unless (defined($i) && defined($j) && defined($sha1) - && $i =~ /^(0|[1-9]\d*)$/ && $j =~ /^(0|[1-9]\d*)$/ - && $sha1 =~ /^[0-9a-f]{40}$/); - - my $i2 = int($i / 2); - my $j2 = int($j / 2); - my $k = pack('II', $i2, $j2); - if (!exists($halfrestiles{$k})) { - ++$nhalfrestiles; - $halfrestiles{$k} = undef; - } - - $fullrestiles{pack('II', $i, $j)} = $sha1; - ++$ntiles; -} while (defined($line = $in_index->getline())); - -die "$inputdir/index: $!" if ($in_index->error()); -$in_index->close(); -debug("done\n"); - -debug("read $ntiles from index\n"); -debug("have $nhalfrestiles to generate\n"); - -System("ppmmake '#ffffff' 254 254 > $outputdir/blank.ppm"); - -sub imgname ($) { - my $sha1 = shift; - if (!defined($sha1)) { - return "$outputdir/blank.ppm"; - } else { - $sha1 =~ s#^(.)(.)(.)(.+)$#$1/$2/$3/$1$2$3$4#; - return "<( pngtopnm $inputdir/tiles/$sha1.png )"; - } -} - -my %compose = ( ); -my $nnew = 0; -my $nduplicates = 0; -my $size = 0; -foreach my $k (keys(%halfrestiles)) { - my ($i2, $j2) = unpack('II', $k); - my @t = ( ); - my $n = 0; - for (my $j = 0; $j <= 1; ++$j) { - for (my $i = 0; $i <= 1; ++$i) { - $t[$n] = $fullrestiles{pack('II', 2 * $i2 + $i, 2 * $j2 + $j)}; -# debug("img #$n is ", defined($t[$n]) ? $t[$n] : '(blank)', "\n"); - ++$n; - } - } - - my $c = join(',', map { defined($_) ? $_ : '*' } @t); - my $fn = "$outputdir/out.png"; - my $sha; - - if (exists($compose{$c})) { - # we've already made this image - $sha = $compose{$c}; - } else { - # make it - @t = map { imgname($_) } @t; - System("pnmcat -tb <( pnmcat -lr $t[0] $t[1] ) <( pnmcat -lr $t[2] $t[3] )" - . " | pnmscale -reduce 2 2>/dev/null" - . " | pnmtopng -compression 9 2>/dev/null > $fn"); - - my $f = new IO::File($fn, O_RDONLY) - or die "$fn: $!"; - $sha = new Digest::SHA1(); - $sha->addfile($f); - $sha = $sha->hexdigest(); - $f->close(); - - $compose{$c} = $sha; - } - - $index->printf("%d %d %s\n", $i2, $j2, $sha); - my ($n1, $n2, $n3) = ($sha =~ /^(.)(.)(.)/); - mkdir("$outputdir/tiles/$n1"); - mkdir("$outputdir/tiles/$n1/$n2"); - mkdir("$outputdir/tiles/$n1/$n2/$n3"); - my $fn2 = "$outputdir/tiles/$n1/$n2/$n3/$sha.png"; - if (-e $fn2) { - unlink($fn); - ++$nduplicates; - } else { - rename($fn, $fn2) or die "rename: $!"; - ++$nnew; - my $st = stat($fn2); - $size += $st->size(); - } - - debug("have $nnew new, $nduplicates duplicates, total size $size bytes\n"); -} - -# get rid of the blank PPM. -unlink("$outputdir/blank.ppm"); diff --git a/maketiles/README b/maketiles/README deleted file mode 100644 index 64c6bb380..000000000 --- a/maketiles/README +++ /dev/null @@ -1,28 +0,0 @@ -File formats and structures - -Tiles are generated as PNG files. At the moment these are full-colour PNGs but -in due course we will probably want to use dithered images with a limited -palette. Generated tiles are named for their own SHA1 checksums, so that we -can eliminate duplicate tiles. The first-stage output of these scripts, -therefore, consists of a set of PNG files named by SHA1 and a text index which -maps tile locations to SHA1s. These are arranged as: - - toplevel/index text index of tiles - toplevel/tiles/0/0/0 location for images with SHA1s beginning - 000 - toplevel/tiles/0/0/0/00000000000000000000.png - first possible image file - -This format is not really acceptable for final deployment because many of the -image files won't be much larger than a filesystem block, so a large amount of -disk space would be wasted in storing them. Nor is the text index from tile -location to SHA1 acceptable. So a second program packs the image files into -catenated files with an index from SHA1 to offset and length in each file. -Separately, the index from tile location to SHA1 is turned into another index. -Both indices use Bernstein-style CDB files. - - toplevel/index.cdb binary index from tile location to SHA1 - toplevel/tiles/0/0/0/packed catenated file of images with SHA1s - beginning 000 - toplevel/tiles/0/0/0/index.cdb index of images in packed - diff --git a/maketiles/fixindex b/maketiles/fixindex deleted file mode 100755 index 8bfed69db..000000000 --- a/maketiles/fixindex +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/perl -w -# -# fixindex: -# Fix the indices generated by the buggy version of 10kfull. -# -# Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# - -my $rcsid = ''; $rcsid .= '$Id: fixindex,v 1.2 2006-09-20 15:20:30 chris Exp $'; - -use strict; - -use constant SUBTILES_PER_TILE => 31; - -while (defined(my $line = <STDIN>)) { - chomp($line); - if ($line =~ /^(0|[1-9]\d*) (0|[1-9]\d*) ([0-9a-f]+)$/) { - my ($x, $y, $id) = ($1, $2, $3); - $y = int($y / SUBTILES_PER_TILE) * SUBTILES_PER_TILE - + SUBTILES_PER_TILE - 1 - ($y % SUBTILES_PER_TILE); - print "$x $y $id\n"; - } else { - print "$line\n"; - } -} diff --git a/maketiles/packtiles b/maketiles/packtiles deleted file mode 100755 index dd12ae94f..000000000 --- a/maketiles/packtiles +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/perl -w -# -# packtiles: -# Pack a tree of tiles etc. as generated by 10kfull etc. into the more efficient -# packed format which is read by the tile server. -# -# Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# - -my $rcsid = ''; $rcsid .= '$Id: packtiles,v 1.3 2006-09-20 14:57:42 chris Exp $'; - -use strict; - -use CDB_File; -use Errno; -use File::Slurp; -use File::stat; -use IO::Dir; -use IO::File; - -use constant TILEID_LEN => 20; -use constant BLOCKING => 14; - -sub debug (@) { - print STDERR @_; -} - -sub pack_tile_dir ($$) { - my ($indir, $outdir) = @_; - -} - -# new_index_block -# -sub new_index_block () { - # Initially all tiles are null so all bits are set. - my $bitmaplen = int((BLOCKING * BLOCKING + 8) / 8); - my $buf = "\xff" x $bitmaplen; - $buf .= "\00" x (BLOCKING * BLOCKING * TILEID_LEN); -} - -# add_tile_to_index_block BLOCK X Y ID -# -sub add_tile_to_index_block ($$$$) { - my $x = $_[1]; - my $y = $_[2]; - my $id = $_[3]; - - # clear the nullness bit. - my $off = $x + $y * BLOCKING; - my $b = unpack('C', substr($_[0], $off >> 3, 1)); - $b &= ~(1 << ($off & 7)); - substr($_[0], $off >> 3, 1) = pack('C', $b); - - # stick the tile itself in. - my $bitmaplen = int((BLOCKING * BLOCKING + 8) / 8); - substr($_[0], $bitmaplen + $off * TILEID_LEN, TILEID_LEN) = $id; -} - -die "arguments are input (unpacked) tile directory, and output (packed)\n" - . "tileset directories\n" - if (@ARGV != 2); - -my ($inputdir, $outputdir) = @ARGV; -$inputdir =~ s#/$##; -$outputdir =~ s#/$##; -die "$inputdir: not a directory" if (!-d $inputdir); -die "$outputdir: not a directory" if (!-d $outputdir); - -# first make the index of tile locations to IDs. -my %index; -debug("reading index of tile locations to IDs...\n"); -my $f = new IO::File("$inputdir/index", O_RDONLY); -my $len = stat($f)->size(); -my $n = 0; -while (defined(my $line = $f->getline())) { - debug("\rreading index: ", $f->tell(), "/$len bytes") - if (0 == ($n % 100)); - ++$n; - - next if ($line =~ /^#/); - - chomp($line); - my ($x, $y, $hash) = split(/ /, $line); - my $id = pack('H*', $hash); - - my $x2 = int($x / BLOCKING); - my $y2 = int($y / BLOCKING); - - $index{"$x2,$y2"} ||= new_index_block(); - add_tile_to_index_block( - $index{"$x2,$y2"}, - $x % BLOCKING, $y % BLOCKING, - $id); -} -debug("\rreading index: $len/$len bytes\n"); - -die "$inputdir/index: $!" if ($f->error()); -$f->close(); - -debug("writing index blocks to index.cdb... "); -my $C = new CDB_File("$outputdir/index.cdb", "$outputdir/index.new"); -$C->insert(blocking => BLOCKING); -foreach (keys(%index)) { - $C->insert($_, $index{$_}); -} -debug("done\n"); -debug("finalising index.cdb... "); -$C->finish(); -debug("done\n"); - -debug("packing individual tile directories...\n"); -$n = 0; -mkdir("$outputdir/tiles"); -for (my $u = 0; $u < 4096; ++$u) { - my $subpath = sprintf('%x/%x/%x', $u & 0xf, ($u >> 4) & 0xf, $u >> 8); - - next unless (-d "$inputdir/tiles/$subpath"); - - # Ensure the path exists. - foreach my $p (qw(1 3 5)) { - my $d = "$outputdir/tiles/" . substr($subpath, 0, $p); - die "$d: mkdir: $!" if (!mkdir($d) && !$!{EEXIST}); - } - - my $in = "$inputdir/tiles/$subpath"; - my $out = "$outputdir/tiles/$subpath"; - - my $f = new IO::File("$out/tiles", O_WRONLY | O_CREAT | O_TRUNC, 0644) - or die "$out/tiles: open: $!"; - $C = new CDB_File("$out/tiles.cdb", "$out/tiles.tmp"); - my $d = new IO::Dir($in) or die "$in: $!"; - while (my $name = $d->read()) { - next unless ($name =~ /^([0-9a-f]+)\.png$/); - my $id = pack('H*', $1); - my $buf = read_file("$in/$name") or die "$in/$name: $!"; - - die "$in/$name: length of file read by File::Slurp did not match stat" - unless (stat("$in/$name")->size() == length($buf)); - - my $rec = sprintf('%x:%x', $f->tell(), length($buf)); - $C->insert($id, $rec); - - $f->print($buf) or die "$out/tiles: $!"; - - ++$n; - } - - $f->close(); - $C->finish(); - - debug("\rpacking tiles: $u/4096"); -} - -debug("\rpacking tiles: 4096/4096\n"); -debug("packed $n tiles total\n"); diff --git a/pnmtilesplit/Makefile b/pnmtilesplit/Makefile deleted file mode 100644 index 65595b2e5..000000000 --- a/pnmtilesplit/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# -# Makefile: -# Build pnmtilesplit. -# -# Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# -# $Id: Makefile,v 1.2 2006-09-14 17:21:03 chris Exp $ -# - -CFLAGS = -Wall -g -O99 -LDFLAGS = -LDLIBS = -lnetpbm # is this a debianism? - -pnmtilesplit: pnmtilesplit.c - $(CC) $(CFLAGS) pnmtilesplit.c $(LDFLAGS) $(LDLIBS) -o pnmtilesplit - -clean: - rm -f pnmtilesplit *~ core diff --git a/pnmtilesplit/pnmtilesplit.c b/pnmtilesplit/pnmtilesplit.c deleted file mode 100644 index ed1ac5dd8..000000000 --- a/pnmtilesplit/pnmtilesplit.c +++ /dev/null @@ -1,365 +0,0 @@ -/* - * pnmtilesplit.c: - * Split a single large PNM file into numerous smaller tiles. - * - * Copyright (c) 2005 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: pnmtilesplit.c,v 1.10 2006-09-19 11:27:30 chris Exp $"; - -#include <sys/types.h> - -#include <errno.h> -#include <fcntl.h> -#include <pam.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <sys/wait.h> - -#define err(...) \ - do { \ - fprintf(stderr, "pnmtilesplit: "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) - -#define die(...) do { err(__VA_ARGS__); exit(1); } while (0) - -static int verbose; -#define debug(...) if (verbose) fprintf(stderr, __VA_ARGS__) - -/* xmalloc LEN - * Allocate LEN bytes, returning the allocated buffer on success or dying on - * failure. */ -static void *xmalloc(const size_t s) { - void *v; - if (!(v = malloc(s))) - die("malloc(%u): %s", (unsigned)s, strerror(errno)); - return v; -} - -/* open_output_file FORMAT PIPE I J [PID] - * Open a new output file, constructing it from FORMAT and the column- and - * row-index values I and J. If PIPE is non-NULL, open the file via a pipe - * through the shell. Returns a stdio file handle on success or abort on - * failure. If PIPE is non-NULL then the process ID of the child process is - * saved in *PID. */ -static FILE *open_output_file(const char *fmt, const char *pipe_via, - const int i, const int j, pid_t *child_pid) { - FILE *fp; - char *filename; - filename = xmalloc(strlen(fmt) + 64); - sprintf(filename, fmt, i, j); - /* XXX consider creating directories if they don't already exist? */ - if (pipe_via) { - pid_t p; - int fd, pp[2]; - if (-1 == (fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644))) - die("%s: open: %s", filename, strerror(errno)); - else if (-1 == pipe(pp)) - die("pipe: %s", strerror(errno)); - else if (!(fp = fdopen(pp[1], "w"))) - die("fdopen: %s", strerror(errno)); - - if (-1 == (p = fork())) - die("fork: %s", strerror(errno)); - else if (0 == p) { - /* run the pipe command via /bin/sh */ - char *argv[4] = {"/bin/sh", "-c", 0}; - char si[40], sj[40]; - sprintf(si, "TILECOL=%d", i); - putenv(si); - sprintf(sj, "TILEROW=%d", j); - putenv(sj); - close(0); - close(1); - close(pp[1]); - dup(pp[0]); /* standard input */ - close(pp[0]); - dup(fd); /* standard output */ - close(fd); - argv[2] = (char*)pipe_via; - execv(argv[0], argv); - err("%s: %s", pipe_via, strerror(errno)); - _exit(1); - } else if (child_pid) - *child_pid = p; - - close(pp[0]); - close(fd); - - debug("forked child process %d for pipe to \"%s\", write fd = %d\n", - (int)p, filename, pp[1]); - } else if (!(fp = fopen(filename, "w"))) { - die("%s: open: %s", filename, strerror(errno)); - debug("opened file \"%s\"\n", filename); - } - - free(filename); - - return fp; -} - -/* usage STREAM - * Write a usage message to STREAM. */ -void usage(FILE *fp) { - fprintf(fp, -"pnmtilesplit - split a PNM file into fixed-size tiles\n" -"\n" -"Usage: pnmtilesplit -h | [OPTIONS] WIDTH HEIGHT [INPUT]\n" -"\n" -"Split the INPUT image, or, if it is not specified, the image on standard\n" -"input, into WIDTH-by-HEIGHT pixel tiles. If WIDTH or HEIGHT do not divide\n" -"the dimensions of the input image exactly, a warning will be printed and\n" -"the pixels at the extreme right and bottom of the input image will be\n" -"discarded.\n" -"\n" -"Options:\n" -"\n" -" -h Display this help message on standard output.\n" -"\n" -" -v Output debugging information on standard error.\n" -"\n" -" -P Display progress information on standard error (implied by\n" -" -v).\n" -"\n" -" -f FORMAT Use the printf-style FORMAT for the name of the output file,\n" -" instead of \"%%d,%%d.pnm\".\n" -"\n" -" -p COMMAND Don't write files directly, but pipe them via COMMAND. The\n" -" COMMAND is interpreted by the shell. The variables TILECOL\n" -" and TILEROW in the environment of the command are set to\n" -" the column and row indices of the tile being generated.\n" -"\n" -" -s Use a slow but more correct implementation, which processes\n" -" the image files only through netpbm's own API, rather than\n" -" copying pixel data directly between input and output\n" -" images.\n" -"\n" -"Note that you can specify -f /dev/null and use the pipe command to create\n" -"the output images, for instance with a command like,\n" -" pnmtilesplit -p 'pnmtopng > $TILEROW,$TILECOL.png' -f /dev/null 256 256\n" -"if you want to exchange the column and row indices.\n" -"\n" -"Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.\n" -"Email: chris@mysociety.org; WWW: http://www.mysociety.org/\n" -"%s\n", - rcsid); -} - -/* main ARGC ARGV - * Entry point. */ -int main(int argc, char *argv[]) { - int tile_w, tile_h, cols, rows; - char *img_name; - FILE *img_fp, **tile_fp; - struct pam img_pam, *tile_pam; - pid_t *tile_pid; - char *outfile_format = "%d,%d.pnm", *pipe_via = NULL; - extern int opterr, optopt, optind; - static const char optstr[] = "hvPsf:p:"; - int i, j, c, progress = 0, fast_and_ugly = 1; - tuple *img_row = NULL; /* suppress bogus "might be used uninitialised" */ - unsigned char *buf = NULL; - size_t img_rowlen = 0, tile_rowlen = 0; - - pnm_init(&argc, argv); - opterr = 0; - - while (-1 != (c = getopt(argc, argv, optstr))) { - switch (c) { - case 'h': - usage(stdout); - return 0; - - case 'v': - verbose = 1; - /* fall through */ - - case 'P': - progress = 1; - break; - - case 'f': - outfile_format = optarg; - break; - - case 'p': - pipe_via = optarg; - break; - - case 's': - fast_and_ugly = 0; - break; - - case '?': - default: - if (strchr(optstr, optopt)) - err("option -%c requires an argument", optopt); - else - err("unknown option -%c", optopt); - die("try -h for help"); - } - } - - if (argc - optind < 2 || argc - optind > 3) { - err("two or three non-option arguments required"); - die("try -h for help"); - } - - if (0 == (tile_w = atoi(argv[optind]))) - die("\"%s\" is not a valid tile width", argv[optind]); - else if (0 == (tile_h = atoi(argv[optind + 1]))) - die("\"%s\" is not a valid tile height", argv[optind + 1]); - - if (argv[optind + 2]) { - img_name = argv[optind + 2]; - if (!(img_fp = fopen(img_name, "rb"))) { - die("%s: %s", img_name, strerror(errno)); - return 1; - } - } else { - img_name = "(standard input)"; - img_fp = stdin; - } - - /* lamely, this will just abort if something goes wrong */ - pnm_readpaminit(img_fp, &img_pam, sizeof img_pam); - - /* couple of checks on the image dimensions */ - if (tile_w > img_pam.width) - die("image width (%d) is smaller than tile width (%d)", - img_pam.width, tile_w); - else if (img_pam.width % tile_w) { - err("warning: tile width does not divide image width exactly"); - err("warning: last %d columns of image will not be included in any tile", - img_pam.width % tile_w); - } - cols = img_pam.width / tile_w; - - if (tile_h > img_pam.height) - die("image height (%d) is smaller than tile height (%d)", - img_pam.height, tile_h); - else if (img_pam.height % tile_h) { - err("warning: tile height does not divide image height exactly"); - err("warning: last %d rows of image will not be included in any tile", - img_pam.height % tile_h); - } - rows = img_pam.height / tile_h; - - debug("input image is %d by %d pixels\n", img_pam.width, img_pam.height); - debug(" = %d by %d tiles of %d by %d", cols, rows, tile_w, tile_h); - debug("each pixel contains %d planes, %d bytes per sample", - img_pam.depth, img_pam.bytes_per_sample); - - tile_fp = xmalloc(cols * sizeof *tile_fp); - tile_pam = xmalloc(cols * sizeof *tile_pam); - tile_pid = xmalloc(cols * sizeof *tile_pid); - - if (fast_and_ugly) { - img_rowlen = img_pam.width * img_pam.depth * img_pam.bytes_per_sample; - buf = xmalloc(img_rowlen); - tile_rowlen = tile_w * img_pam.depth * img_pam.bytes_per_sample; - } else if (!(img_row = pnm_allocpamrow(&img_pam))) - die("unable to allocate storage for input row"); - - for (j = 0; j < rows; ++j) { - int y; - - /* Create output files. */ - debug("creating output files for row %d/%d...\n", j, rows); - for (i = 0; i < cols; ++i) { - tile_pam[i] = img_pam; - tile_pam[i].file = tile_fp[i] - = open_output_file(outfile_format, pipe_via, i, j, - tile_pid + i); - tile_pam[i].width = tile_w; - tile_pam[i].height = tile_h; - pnm_writepaminit(tile_pam + i); - fflush(tile_fp[i]); - } - - /* Copy the image into the various tiles. */ - for (y = 0; y < tile_h; ++y) { - /* Ugly. libpnm is pretty slow, so for large images it is much - * quicker to copy bytes from input to output streams using - * straight stdio calls. If fast_and_ugly is true (the default) - * then we use such an implementation; but it makes assumptions - * about the format of the input PNM file which might not be - * accurate. So we make this optional. */ - if (fast_and_ugly) { - size_t n; - if (img_rowlen != (n = fread(buf, 1, img_rowlen, img_fp))) { - if (feof(img_fp)) - die("%s: premature EOF", img_name); - else - die("%s: %s", img_name, strerror(errno)); - } - for (i = 0; i < cols; ++i) { - if (tile_rowlen - != (n = fwrite(buf + i * tile_rowlen, - 1, tile_rowlen, tile_fp[i]))) { - die("while writing tile (%d, %d): %s", i, j, - strerror(errno)); - } - fflush(tile_fp[i]); - } - } else { - pnm_readpamrow(&img_pam, img_row); - for (i = 0; i < cols; ++i) { - pnm_writepamrow(tile_pam + i, img_row + i * tile_w); - fflush(tile_fp[i]); - } - } - if (progress) - fprintf(stderr, "\r%d/%d", j * tile_h + y, img_pam.height); - } - - /* Close the output files and check status. */ - debug("\rclosing output files for row %d/%d...\n", j, rows); - for (i = 0; i < cols; ++i) { - debug("closing fd %d... ", fileno(tile_fp[i])); - if (-1 == fclose(tile_fp[i])) - die("while writing tile (%d, %d): %s", i, j, strerror(errno)); - debug("done\n"); - } - - /* XXX I think there is a bug here, since if you close fd i, then - * wait for child process i, the wait hangs. But I can't see the - * problem at the moment. Actually calling wait synchronously here - * is bogus anyway, since we could collect the notification when we - * receive SIGCHLD, though that would risk spawning a vast number - * of processes; at the moment we maintain as many child processes - * as there are columns of tiles in the output. */ - if (pipe_via) { - debug("waiting for termination of child processes\n"); - for (i = 0; i < cols; ++i) { - /* Collect exit status of child process. */ - pid_t p; - int st; - debug("waiting for termination of process %d... ", - (int)tile_pid[i]); - if (-1 == (p = waitpid(tile_pid[i], &st, 0))) - die("waitpid: %s", strerror(errno)); - else if (st) { - if (WIFEXITED(st)) - die("child process for tile (%d, %d) failed with " - "status %d", i, j, WEXITSTATUS(st)); - else - die("child process for tile (%d, %d) killed by " - "signal %d", i, j, WTERMSIG(st)); - } - debug("exited\n"); - } - } - } - - if (progress) - fprintf(stderr, "\r%d/%d\n", img_pam.height, img_pam.height); - return 0; -} diff --git a/tileserver/Makefile b/tileserver/Makefile deleted file mode 100644 index b9ffc302f..000000000 --- a/tileserver/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -# -# Makefile: -# Build tile server. -# -# Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# -# $Id: Makefile,v 1.1 2006-09-20 15:45:51 chris Exp $ -# - -CFLAGS = -Wall -g -O99 -I/software/include -LDFLAGS = -L/software/lib -LDLIBS = -lfcgi - -# for debugging -#CFLAGS += -DNO_FASTCGI - -SRCS = base64.c cdb.c netstring.c tileserver.c tileset.c util.c -OBJS = $(SRCS:.c=.o) -HDRS = base64.h cdb.h netstring.h tileset.h util.h -TXTS = - -tileserver: Makefile $(OBJS) - $(CC) -o tileserver $(OBJS) $(LDFLAGS) $(LDLIBS) - -clean: - rm -f tileserver $(OBJS) *~ core - -%.o: %.c Makefile - $(CC) $(CFLAGS) -c -o $@ $< - diff --git a/tileserver/base64.c b/tileserver/base64.c deleted file mode 100644 index 08723e31c..000000000 --- a/tileserver/base64.c +++ /dev/null @@ -1,272 +0,0 @@ -/* - * base64.c: - * Base64 and "base64ish" encoding and decoding. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: base64.c,v 1.1 2006-09-20 10:25:14 chris Exp $"; - -#include <stdbool.h> -#include <stdint.h> -#include <string.h> - -static const char b64chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "+/="; - -static const char b64ishchars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "-_="; - -/* base64_encode IN INLEN OUT B64ISH NOPAD - * Encode INLEN bytes at IN into the buffer at OUT. The total number of bytes - * written is recorded in *OUTLEN. OUT must have space for at least - * 1 + 4 * (INLEN + 3) / 3 + 1 bytes. Returns a pointer to OUT. This function - * always succeeds. If B64ISH is true, the alternate "base64ish" alphabet is - * used instead of the standard one. If NOPAD is true, "=" padding is not added - * at the end of the transformed buffer. */ -char *base64_encode(const void *in, const size_t inlen, char *out, - const bool b64ish, const bool nopad) { - const char *alphabet; - const uint8_t *b; - char *p; - size_t i; - - alphabet = b64ish ? b64ishchars : b64chars; - b = (const uint8_t*)in; - - for (i = 0, p = out; i < inlen; i += 3) { - uint8_t bb[3] = {0}; - unsigned j; - size_t n, k; - - n = inlen - i; - if (n > 3) n = 3; - for (k = 0; k < n; ++k) bb[k] = b[i + k]; - - j = bb[0] >> 2; - *(p++) = alphabet[j]; - - j = ((bb[0] & 3) << 4) | (bb[1] >> 4); - *(p++) = alphabet[j]; - - if (n == 1) { - if (!nopad) { - *(p++) = '='; - *(p++) = '='; - } - break; - } - - j = ((bb[1] & 0xf) << 2) | (bb[2] >> 6); - *(p++) = alphabet[j]; - if (n == 2) { - if (!nopad) - *(p++) = '='; - break; - } - - j = bb[2] & 0x3f; - *(p++) = alphabet[j]; - } - - *p = 0; - - return out; -} - -/* base64_decode IN OUT OUTLEN B64ISH - * Decode the string at IN into OUT. If B64ISH is true, the alternate - * "base64ish" alphabet is used instead of the standard one. Returns the number - * of characters consumed and saves the number of output bytes decoded in - * *OUTLEN; the number of characters consumed will be smaller than the length - * of the input string if an invalid character was encountered in IN. OUT must - * have space for at least 3 * (INLEN / 4) bytes of output. */ -size_t base64_decode(const char *in, void *out, size_t *outlen, - const bool b64ish) { - const char *alphabet; - uint8_t *b; - size_t inlen = 0, consumed = 0, len = 0, i; - - inlen = strlen(in); - alphabet = b64ish ? b64ishchars : b64chars; - b = (uint8_t*)out; - - for (i = 0; i < inlen; i += 4) { - char bb[5] = "===="; - size_t n, j; - const char *p; - - n = inlen - i; - if (n > 4) n = 4; - memcpy(bb, in + i, n); - - if (!(p = strchr(alphabet, bb[0]))) - break; - j = p - alphabet; - b[len] = (uint8_t)(j << 2); - ++consumed; - - if (!(p = strchr(alphabet, bb[1]))) - break; - j = p - alphabet; - b[len++] |= (uint8_t)(j >> 4); - b[len] = (uint8_t)(j << 4); - ++consumed; - - if ('=' == bb[2]) { - ++consumed; - if ('=' == *p) ++consumed; /* potentially skip last char */ - break; - } else if (!(p = strchr(alphabet, bb[2]))) - break; - j = p - alphabet; - b[len++] |= (uint8_t)(j >> 2); - b[len] = (uint8_t)(j << 6); - ++consumed; - - if ('=' == bb[3]) { - ++consumed; - break; - } else if (!(p = strchr(alphabet, bb[3]))) - break; - j = p - alphabet; - b[len++] |= (uint8_t)j; - ++consumed; - } - - *outlen = len; - return consumed; -} - -#ifdef BASE64_TEST_PROGRAM - -/* - * Small test program -- reads base64-encoded or raw data on standard input, - * and writes on standard output the decoded/encoded version. Driven by - * base64test. - */ - -#include <ctype.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> - -#define err(...) \ - do { \ - fprintf(stderr, "base64test: "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) -#define die(...) do { err(__VA_ARGS__); exit(1); } while (0) - -struct datum { - void *buf; - size_t len; -}; - -static struct datum *netstring_read(FILE *fp) { - unsigned int len = 0; - int c; - struct datum *d; - -#define FAIL(what) \ - do { \ - if (feof(fp)) \ - die("%s: Premature EOF", what); \ - else \ - die("%s: %s", what, strerror(errno)); \ - } while (0) - - while (EOF != (c = getc(fp))) { - if (isdigit(c)) - len = 10 * len + c - '0'; - else if (c == ':') - break; - else - die("bad character '%c' in netstring length", c); - } - - if (feof(fp) || ferror(fp)) - FAIL("while reading netstring length"); - - if (!(d = malloc((sizeof *d) + len + 1))) - die("malloc: %s", strerror(errno)); - d->buf = (char*)d + (sizeof *d); - d->len = len; - ((char*)d->buf)[len] = 0; /* ensure NUL-terminated */ - - if (d->len != fread(d->buf, 1, d->len, fp)) - FAIL("while reading netstring data"); - - if (EOF == (c = getc(fp))) { - if (feof(fp)) - die("while reading netstring trailer: Premature EOF"); - else - die("while reading netstring trailer: %s", strerror(errno)); - } - - return d; -} - -void netstring_write(FILE *fp, const struct datum *d) { - fprintf(fp, "%u:", (unsigned)d->len); - if (d->len != fwrite(d->buf, 1, d->len, fp)) - die("while writing netstring value: %s", strerror(errno)); - if (1 != fprintf(fp, ",")) - die("while writing netstring trailer: %s", strerror(errno)); -} - -/* main ARGC ARGV - * Entry point. */ -int main(int argc, char *argv[]) { - while (1) { - int c; - struct datum *d, d2; - size_t l; - - if (EOF == (c = getc(stdin))) - die("premature EOF reading command character"); - else if ('X' == c) - break; - - d = netstring_read(stdin); - - switch (c) { - case 'B': /* base64 */ - case 'b': /* base64ish */ - if (!(d2.buf = malloc(d->len))) - die("malloc: %s", strerror(errno)); - l = base64_decode(d->buf, d2.buf, &d2.len, c == 'b'); - netstring_write(stdout, &d2); - free(d2.buf); - break; - - case 'R': /* to base64 */ - case 'r': /* to base64ish */ - if (!(d2.buf = malloc(1 + 4 * (1 + d->len / 3)))) - die("malloc: %s", strerror(errno)); - base64_encode(d->buf, d->len, d2.buf, c == 'r', 0); - d2.len = strlen((char*)d2.buf); - netstring_write(stdout, &d2); - free(d2.buf); - break; - - default: - die("bad command character '%c'", c); - } - - free(d); - fflush(stdout); - } - return 0; -} - -#endif /* BASE64_TEST_PROGRAM */ diff --git a/tileserver/base64.h b/tileserver/base64.h deleted file mode 100644 index 3c61cabc3..000000000 --- a/tileserver/base64.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * base64.h: - * Base64 and "base64ish" encoding and decoding. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - * $Id: base64.h,v 1.2 2006-09-20 13:22:58 chris Exp $ - * - */ - -#ifndef __BASE64_H_ /* include guard */ -#define __BASE64_H_ - -#include <sys/types.h> - -#include <stdbool.h> - -/* base64.c */ -char *base64_encode(const void *in, const size_t inlen, char *out, - const bool b64ish, const bool nopad); -size_t base64_decode(const char *in, void *out, size_t *outlen, - const bool b64ish); - -#endif /* __BASE64_H_ */ diff --git a/tileserver/cdb.c b/tileserver/cdb.c deleted file mode 100644 index 2f9667f2d..000000000 --- a/tileserver/cdb.c +++ /dev/null @@ -1,476 +0,0 @@ -/* - * cdb.c: - * Read data from Dan-Bernstein-style CDB files. - * - * See: http://cr.yp.to/cdb/cdb.txt -- but note also the statement in - * http://cr.yp.to/cdb/reading.html that, "There may be several records under a - * single key. You can use cdb_findnext to find the next record under this - * key." We don't support (or use) this mode, but it should probably be added - * to the code for completeness. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: cdb.c,v 1.6 2006-09-22 12:25:46 francis Exp $"; - -#include <sys/types.h> - -#include <errno.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <sys/stat.h> - -#define CDB_IMPL -#include "cdb.h" - -#ifdef VERBOSE_DEBUGGING -# define debug(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) -#else -# define debug(...) do { } while (0) -#endif /* VERBOSE_DEBUGGING */ - -/* struct cdb - * Internals of CDB object. */ -struct cdb { - FILE *c_fp; - struct stat c_st; - bool c_close_on_destroy; - struct { - uint32_t off, len; - } c_hashlocs[256]; -}; - -/* Length of the initial portion of the file which gives the hash table - * locations. */ -#define HASHPTR_LEN (256 * 8) - -static size_t do_fread(FILE *fp, void *buf, const size_t len) { - return fread(buf, 1, len, fp); -} - -/* cdb_hash BUF LEN - * Return the hash value of the LEN bytes at BUF. */ -cdb_hash_t cdb_hash(const unsigned char *buf, const size_t len) { - uint32_t h = 5381; - size_t i; - for (i = 0; i < len; ++i) { - h = ((h << 5) + h) ^ buf[i]; - } - return h; -} - -/* cdb_hash_str STRING - * Return the hash value of the given STRING. */ -cdb_hash_t cdb_hash_str(const char *s) { - return cdb_hash((unsigned char*)s, strlen(s)); -} - -/* cdb_hash_datum D - * Return the hash value of the DATUM. */ -cdb_hash_t cdb_hash_datum(const cdb_datum d) { - return cdb_hash(d->cd_buf, d->cd_len); -} - -/* cdb_open_fp FP - * Open a CDB file for which a stdio file pointer FP is available. Returns a - * cdb object on success or NULL on failure, setting cdb_errno. */ -cdb cdb_open_fp(FILE *fp) { - struct cdb *C = NULL, Cz = {0}; - unsigned char buf[HASHPTR_LEN]; - struct stat st; - int i; - -#define FAIL(e) do { cdb_errno = e; goto fail; } while (0) - - if (-1 == fstat(fileno(fp), &st)) - FAIL(errno); - - if (st.st_size < HASHPTR_LEN) - FAIL(CDB_FILE_TOO_SMALL); - - if (!(C = malloc(sizeof *C))) - FAIL(CDB_OUT_OF_MEMORY); - - /* wording conditional like this prevents a warning with 32-bit off_t */ - if ((st.st_size >> 31) > 1) - FAIL(CDB_FILE_TOO_BIG); - - *C = Cz; - C->c_fp = fp; - C->c_st = st; - - if (HASHPTR_LEN != do_fread(fp, buf, HASHPTR_LEN)) { - if (feof(fp)) - FAIL(CDB_FILE_TRUNCATED); - else - FAIL(errno); - } - - for (i = 0; i < 256; ++i) { - memcpy(&C->c_hashlocs[i].off, buf + 8 * i, 4); - memcpy(&C->c_hashlocs[i].len, buf + 8 * i + 4, 4); - /* byte ordering -- CDB is defined as a little-endian format, so - * this is fine on i386, but not elsewhere. */ - /* NB len is in slots not bytes */ - if (C->c_hashlocs[i].off < HASHPTR_LEN - || C->c_hashlocs[i].off > C->c_st.st_size - || C->c_st.st_size - C->c_hashlocs[i].off < C->c_hashlocs[i].len * 8) - FAIL(CDB_BAD_HASHLOC_PTR); - } - - return C; - -fail: - free(C); - return NULL; - -#undef FAIL -} - -/* yuk */ -#define ALIGNMENT 4 /* __alignof(long double) on i386 */ -typedef uint32_t ptr_int_t; /* XXX needs changing on 64-bit architectures */ - -/* cdb_datum_alloc LEN - * Allocate space for a single cdb_datum holding up to LEN bytes. Free with - * cdb_datum_free. Returns the newly-allocated datum on success or NULL on - * failure (out of memory). */ -cdb_datum cdb_datum_alloc(const size_t len) { - cdb_datum d; - if (!(d = malloc((sizeof *d) + ALIGNMENT + len))) { - cdb_errno = CDB_OUT_OF_MEMORY; - return NULL; - } - d->cd_buf = (void*)(((ptr_int_t)d + (sizeof *d) + ALIGNMENT) & ~(ALIGNMENT - 1)); - d->cd_len = len; - return d; -} - -/* cdb_datum_free D - * Free storage associated with D. */ -void cdb_datum_free(cdb_datum d) { - if (d) free(d); -} - -/* cdb_open FILE - * Open the named CDB FILE, returning a cdb object on success or NULL on - * failure, setting cdb_errno. */ -cdb cdb_open(const char *name) { - cdb C = NULL; - FILE *fp; - if (!(fp = fopen(name, "rb"))) { - cdb_errno = errno; - return NULL; - } else if (!(C = cdb_open_fp(fp))) - fclose(fp); - else - C->c_close_on_destroy = 1; - return C; -} - -/* cdb_close C - * Free storage associated with C, and, if it was opened by cdb_open, also - * close the associated file pointer. */ -void cdb_close(cdb C) { - if (!C) return; - if (C->c_close_on_destroy) - fclose(C->c_fp); - free(C); -} - -/* get_slot C OFFSET SLOT HASH WHERE - * Save in *HASH and *WHERE the hash value and offset in the file pointed to by - * the indexed SLOT in the hash table beginning at OFFSET. Returns 0 on success - * or an error code on failure. */ -static cdb_result_t get_slot(cdb C, const uint32_t offset, const uint32_t slot, - cdb_hash_t *hash, uint32_t *where) { - unsigned char buf[8]; - - if (-1 == fseek(C->c_fp, offset + 8 * slot, SEEK_SET)) - return errno; - - if (8 != do_fread(C->c_fp, buf, 8)) { - if (feof(C->c_fp)) - return CDB_FILE_TRUNCATED; - else - return errno; - } - - memcpy(hash, buf, 4); - memcpy(where, buf + 4, 4); - - return 0; -} - -/* cdb_get C KEY - * Look up the database entry identified by KEY. Returns the data retrieved on - * success or NULL on failure, setting cdb_errno. The returned data should be - * freed with cdb_datum_free. */ -/* XXX add a mode where caller supplies storage */ -cdb_datum cdb_get(cdb C, const cdb_datum key) { - cdb_hash_t h, h8, sh; - uint32_t slot0, slot; - cdb_datum val = NULL; - -#define FAIL(e) do { cdb_errno = e; goto fail; } while (0) - - if (key->cd_len > C->c_st.st_size) - FAIL(CDB_NOT_FOUND); - - h = cdb_hash_datum(key); - h8 = h & 0xff; - - debug("key len = %u", (unsigned)key->cd_len); - debug("hash = %08x, hash255 = %02x", (unsigned)h, (unsigned)h8); - - if (!C->c_hashlocs[h8].off || !C->c_hashlocs[h8].len) - FAIL(CDB_NOT_FOUND); - - debug("hash table %u starts at offset %u and has %u slots", - (unsigned)h8, - (unsigned)C->c_hashlocs[h8].off, - (unsigned)C->c_hashlocs[h8].len); - - slot = slot0 = (h >> 8) % C->c_hashlocs[h8].len; - - debug(" %06x %% %u = %u", (unsigned)(h >> 8), C->c_hashlocs[h8].len, (unsigned)slot); - - do { - unsigned char buf[8]; - uint32_t where, keylen, vallen; - cdb_result_t e; - - debug(" looking in slot %u", (unsigned)slot); - - if ((e = get_slot(C, C->c_hashlocs[h8].off, slot, &sh, &where))) - FAIL(e); - - debug(" hash = %08x, offset = %u", (unsigned)sh, (unsigned)where); - - if (sh == h) { - if (0 == where) - FAIL(CDB_NOT_FOUND); - - /* Have a potential slot. Grab the key and value length. */ - if (-1 == fseek(C->c_fp, where, SEEK_SET)) - FAIL(errno); - else if (8 != do_fread(C->c_fp, buf, 8)) { - if (feof(C->c_fp)) - FAIL(CDB_FILE_TRUNCATED); - else - FAIL(errno); - } - - memcpy(&keylen, buf, 4); - memcpy(&vallen, buf + 4, 4); - - debug(" key len = %u, val len = %u", - (unsigned)keylen, (unsigned)vallen); - - if (keylen == key->cd_len) { - size_t i; - for (i = 0; i < key->cd_len; ++i) { - int c; - if (EOF == (c = getc(C->c_fp))) { - if (feof(C->c_fp)) - FAIL(CDB_FILE_TRUNCATED); - else - FAIL(errno); - } else if (c != (int)(((unsigned char*)key->cd_buf)[i])) - break; - } - - if (i == key->cd_len) { - /* Got it. */ - if (!(val = cdb_datum_alloc(vallen + 1))) - FAIL(CDB_OUT_OF_MEMORY); - /* Ensure NUL-terminated. */ - ((char*)val->cd_buf)[vallen] = 0; - val->cd_len--; - if (val->cd_len != do_fread(C->c_fp, val->cd_buf, - val->cd_len)) { - if (feof(C->c_fp)) - FAIL(CDB_FILE_TRUNCATED); - else - FAIL(errno); - } else - return val; - } - } - } - - slot = (slot + 1) % C->c_hashlocs[h8].len; - } while (slot != slot0); - - cdb_errno = CDB_NOT_FOUND; - -fail: - if (val) cdb_datum_free(val); - return NULL; - -#undef FAIL -} - -/* cdb_get_string C STRING - * As for cdb_get, but construct the KEY datum from STRING. */ -cdb_datum cdb_get_string(cdb C, const char *s) { - struct cdb_datum d; - d.cd_len = strlen(s); - d.cd_buf = (void*)s; - return cdb_get(C, &d); -} - -/* cdb_get_buf C BUF LEN - * As for cdb_get, buf construct the KEY datum from BUF and LEN. */ -cdb_datum cdb_get_buf(cdb C, const void *buf, const size_t len) { - struct cdb_datum d; - d.cd_len = len; - d.cd_buf = (void*)buf; - return cdb_get(C, &d); -} - -/* cdb_strerror E - * Return the text of the error message corresponding to E. */ -char *cdb_strerror(const cdb_result_t e) { - if (e > 0) - return strerror(e); - else if (e == 0) - return "Success"; - else { - switch (e) { - case CDB_OUT_OF_MEMORY: - return "Out of memory"; - case CDB_FILE_TOO_SMALL: - return "File is too small to be a valid CDB file"; - case CDB_FILE_TOO_BIG: - return "File is too large to be a valid CDB file"; - case CDB_BAD_HASHLOC_PTR: - return "Bad hash-table location pointer in CDB file header"; - case CDB_BAD_RECORD_PTR: - return "Bad record location pointer in CDB hash table"; - case CDB_NOT_FOUND: - return "Record not found in CDB file"; - default: - return "Unknown CDB internal error code"; - } - } -} - -#ifdef CDB_TEST_PROGRAM - -/* - * Little test program -- reads keys as netstrings on standard input, and - * writes on standard output either "X" for not found, or netstrings giving the - * values of those keys. - */ - -#include <ctype.h> - -#define err(...) \ - do { \ - fprintf(stderr, "cdbtest: "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) -#define die(...) do { err(__VA_ARGS__); exit(1); } while (0) - -static cdb_datum netstring_read(FILE *fp) { - unsigned int len = 0; - int c; - cdb_datum d; - -#define FAIL(what) \ - do { \ - if (feof(fp)) \ - die("%s: Premature EOF", what); \ - else \ - die("%s: %s", what, strerror(errno)); \ - } while (0) - - while (EOF != (c = getc(fp))) { - if (isdigit(c)) - len = 10 * len + c - '0'; - else if (c == ':') - break; - else - die("bad character '%c' in netstring length", c); - } - - if (feof(fp) || ferror(fp)) - FAIL("while reading netstring length"); - - if (!(d = cdb_datum_alloc(len))) - die("while reading netstring: cdb_datum_alloc(%u): %s", - len, cdb_strerror(cdb_errno)); - - if (d->cd_len != do_fread(fp, d->cd_buf, d->cd_len)) - FAIL("while reading netstring data"); - - if (EOF == (c = getc(fp))) { - if (feof(fp)) - die("while reading netstring trailer: Premature EOF"); - else - die("while reading netstring trailer: %s", strerror(errno)); - } - - return d; -} - -void netstring_write(FILE *fp, const cdb_datum d) { - fprintf(fp, "%u:", (unsigned)d->cd_len); - if (d->cd_len != fwrite(d->cd_buf, 1, d->cd_len, fp)) - die("while writing netstring value: %s", strerror(errno)); - if (1 != fprintf(fp, ",")) - die("while writing netstring trailer: %s", strerror(errno)); -} - -/* main ARGC ARGV - * Entry point. */ -int main(int argc, char *argv[]) { - cdb C; - if (argc != 2) - die("single argument should be name of CDB file"); - else if (!(C = cdb_open(argv[1]))) - die("%s: %s", argv[1], cdb_strerror(cdb_errno)); - - while (1) { - cdb_datum key, val; - int c; - - c = getc(stdin); - if ('X' == c) - break; - else - ungetc(c, stdin); - key = netstring_read(stdin); - - if (!(val = cdb_get(C, key))) { - if (CDB_NOT_FOUND == cdb_errno) - putc('X', stdout); - else - die("cdb_get: %s", cdb_strerror(cdb_errno)); - } else { - netstring_write(stdout, val); - cdb_datum_free(val); - } - fflush(stdout); - - cdb_datum_free(key); - } - - cdb_close(C); - - return 0; -} - -#endif /* CDB_TEST_PROGRAM */ diff --git a/tileserver/cdb.h b/tileserver/cdb.h deleted file mode 100644 index e5959297f..000000000 --- a/tileserver/cdb.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * cdb.h: - * Interface to Dan-Bernstein-style CDB files. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - * $Id: cdb.h,v 1.4 2006-09-20 14:24:10 chris Exp $ - * - */ - -#ifndef __CDB_H_ /* include guard */ -#define __CDB_H_ - -#include <sys/types.h> - -#include <stdint.h> -#include <stdio.h> - -typedef int cdb_result_t; -typedef uint32_t cdb_hash_t; - -typedef struct cdb_datum { - void *cd_buf; - size_t cd_len; -} *cdb_datum; - -typedef struct cdb *cdb; - -/* error codes */ -#define CDB_OUT_OF_MEMORY -1 -#define CDB_FILE_TOO_SMALL -2 -#define CDB_FILE_TOO_BIG -3 -#define CDB_FILE_TRUNCATED -4 - /* one of the initial 256 pointers pointed outside the file */ -#define CDB_BAD_HASHLOC_PTR -5 - /* a datum pointer pointed outside the file or otherwise somewhere bogus */ -#define CDB_BAD_RECORD_PTR -6 - /* the record wasn't found */ -#define CDB_NOT_FOUND -7 - -#ifndef CDB_IMPL -extern -#endif /* CDB_IMPL */ - cdb_result_t cdb_errno; /* XXX threads */ - -/* cdb.c */ -cdb_hash_t cdb_hash(const unsigned char *buf, const size_t len); -cdb_hash_t cdb_hash_str(const char *s); -cdb_hash_t cdb_hash_datum(const cdb_datum d); -cdb cdb_open_fp(FILE *fp); -cdb cdb_open(const char *name); -void cdb_close(cdb C); -cdb_datum cdb_datum_alloc(const size_t len); -void cdb_datum_free(cdb_datum d); -cdb_datum cdb_get(cdb C, const cdb_datum key); -cdb_datum cdb_get_string(cdb C, const char *str); -cdb_datum cdb_get_buf(cdb C, const void *buf, const size_t len); -char *cdb_strerror(const cdb_result_t e); - -#endif /* __CDB_H_ */ diff --git a/tileserver/cdbtestrun b/tileserver/cdbtestrun deleted file mode 100755 index a83c23e60..000000000 --- a/tileserver/cdbtestrun +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/perl -w -# -# cdbtestrun: -# Run the CDB test program. -# -# Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. -# Email: chris@mysociety.org; WWW: http://www.mysociety.org/ -# - -my $rcsid = ''; $rcsid .= '$Id: cdbtestrun,v 1.1 2006-09-19 18:26:35 chris Exp $'; - -use strict; - -use CDB_File; -use IO::Pipe; -use POSIX qw(); - -sub random_string ($) { - my $len = shift; - my $x = ''; - while (length($x) < $len) { - $x .= pack('N', int(rand(0xffffffff))); - } - return substr($x, 0, $len); -} - -my %h; -my $C = new CDB_File("test.cdb", "test.tmp") - or die "test.tmp: $!"; - -my $nkeys = shift(@ARGV); -$nkeys ||= 100000; -die "'$nkeys' is not a valid number of keys" if ($nkeys !~ /^[1-9]\d*$/); - -for (my $i = 0; $i < $nkeys; ++$i) { - my $key; - do { - $key = random_string(1 + int(rand(16))); - } while (exists($h{$key})); - my $val = random_string(int(rand(100))); - - $h{$key} = $val; - $C->insert($key, $val); - - printf STDERR "\rwriting keys: %d/%d", $i, $nkeys - if (0 == ($i % 100)); -} - -print STDERR "\rwriting keys: $nkeys/$nkeys\n"; -print STDERR "finalising database... "; -$C->finish(); -print STDERR "done\n"; - -# Make a pipe to the test program and fork it. -my $p1 = new IO::Pipe(); # parent writes to this -my $p2 = new IO::Pipe(); # and reads from this. - -my $pid = fork(); -if (!defined($pid)) { - die "fork: $!"; -} elsif (0 == $pid) { - $p1->reader(); - POSIX::close(0); - POSIX::dup($p1->fileno()); - $p2->writer(); - POSIX::close(1); - POSIX::dup($p2->fileno()); - -# { exec("valgrind", "./cdbtest", "test.cdb"); } - { exec("./cdbtest", "test.cdb"); } - - print STDERR "exec ./cdbtest: $!\n"; - POSIX::_exit(255); -} - -sub netstring_read ($) { - my $h = shift; - my $len = 0; - while (defined(my $c = $h->getc())) { - if ($c eq 'X' && $len == 0) { - return undef; - } elsif ($c eq ':') { - last; - } elsif ($c !~ /^\d$/) { - die "bad character '$c' in netstring length"; - } else { - $len = $len * 10 + ord($c) - ord('0'); - } - } - - my $buf = ''; - $h->read($buf, $len, 0); - - if ($h->getc() ne ',') { - die "bad character at netstring trailer"; - } - - return $buf; -} - -sub netstring_write ($$) { - my $h = shift; - $h->printf('%d:', length($_[0])); - $h->print($_[0]); - $h->print(','); - $h->flush(); -} - -$p1->writer(); -$p2->reader(); - -print STDERR "child pid = $pid... press enter to continue\n"; -<STDIN>; - -my $n = 0; -foreach my $key (keys(%h)) { - if (rand() < 0.05) { - my $key; - do { - $key = random_string(1 + int(rand(16))); - } while (exists($h{$key})); - netstring_write($p1, $key); - my $val = netstring_read($p2); - if (defined($val)) { - die "test code said value present when it wasn't"; - } - } - - netstring_write($p1, $key); - my $val = netstring_read($p2); - if (!defined($val)) { - die "test code said val was not present when it was"; - } elsif ($val ne $h{$key}) { - print "GOT=$val\n"; - print "WANTED=$val\n"; - die "value mismatch"; - } - - ++$n; - printf STDERR "\rtesting %d/%d", $n, $nkeys - if (0 == ($n % 100)); -} -print STDERR "\rtesting $n/$nkeys\n"; - -$p1->write('X'); -$p1->flush(); -wait(); - -print STDERR "completed with no errors\n"; diff --git a/tileserver/netstring.c b/tileserver/netstring.c deleted file mode 100644 index f6bbc3b72..000000000 --- a/tileserver/netstring.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * netstring.c: - * Write netstrings to strings. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: netstring.c,v 1.2 2006-09-20 13:24:08 chris Exp $"; - -#include <sys/types.h> -#include <stdio.h> -#include <string.h> - -/* netstring_write OUT BUFFER LEN - * Write the LEN-byte BUFFER to OUT as a netstring, returning the number of - * bytes. If OUT is NULL, returns the number of bytes required. */ -size_t netstring_write(char *out, const void *buf, const size_t len) { - size_t l = 0; - char dummy[32]; - l += sprintf(out ? out : dummy, "%u:", (unsigned)len); - if (out) memcpy(out + l, buf, len); - l += len; - if (out) out[l] = ','; - ++l; - return l; -} - -/* netstring_write_string OUT STRING - * Write the NUL-terminated STRING to OUT as a netstring, returning the number - * of bytes used. If OUT is NULL, return the number of bytes required. */ -size_t netstring_write_string(char *out, const char *str) { - return netstring_write(out, str, strlen(str)); -} - -/* netstring_write_string OUT I - * Write I to OUT as a decimal integer formatted as a netstring, returning the - * number of bytes used. If OUT is NULL, return the number of bytes required. */ -size_t netstring_write_int(char *out, const int i) { - char str[32]; - sprintf(str, "%d", i); - return netstring_write_string(out, str); -} diff --git a/tileserver/netstring.h b/tileserver/netstring.h deleted file mode 100644 index 83d5aabfc..000000000 --- a/tileserver/netstring.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * netstring.h: - * Write netstrings into strings. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - * $Id: netstring.h,v 1.1 2006-09-20 10:25:14 chris Exp $ - * - */ - -#ifndef __NETSTRING_H_ /* include guard */ -#define __NETSTRING_H_ - -#include <sys/types.h> - -/* netstring.c */ -size_t netstring_write(char *out, const void *buf, const size_t len); -size_t netstring_write_string(char *out, const char *str); -size_t netstring_write_int(char *out, const int i); - -#endif /* __NETSTRING_H_ */ diff --git a/tileserver/tileserver.c b/tileserver/tileserver.c deleted file mode 100644 index 672319c11..000000000 --- a/tileserver/tileserver.c +++ /dev/null @@ -1,524 +0,0 @@ -/* - * tileserver.c: - * Serve map tiles and information about map tiles. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: tileserver.c,v 1.10 2006-09-22 12:25:43 chris Exp $"; - -/* - * This is slightly complicated by the fact that we indirect tile references - * via hashes of the tiles themselves. We support the following queries: - * - * http://host/path/tileserver/TILESET/HASH - * to get an individual tile image in TILESET identified by HASH; - * http://host/path/tileserver/TILESET/E,N/FORMAT - * to get the identity of the tile at (E, N) in TILESET in the given - * FORMAT; - * http://host/path/tileserver/TILESET/W-E,S-N/FORMAT - * to get the identities of the tiles in the block with SW corner (W, S) - * and NE corner (E, N) in the given FORMAT. - * - * What FORMATS should we support? RABX and JSON are the obvious ones I guess. - * Add TEXT for debugging. - */ - -#include <sys/types.h> - -#include <errno.h> -#ifndef NO_FASTCGI -#include <fcgi_stdio.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#include <sys/stat.h> - -#include "base64.h" -#include "netstring.h" -#include "tileset.h" -#include "util.h" - -/* tiles_basedir - * The configured path to tilesets. */ -static char *tiles_basedir; - -#define HTTP_BAD_REQUEST 400 -#define HTTP_UNAUTHORIZED 401 -#define HTTP_FORBIDDEN 403 -#define HTTP_NOT_FOUND 404 -#define HTTP_INTERNAL_SERVER_ERROR 500 -#define HTTP_NOT_IMPLEMENTED 501 -#define HTTP_SERVICE_UNAVAILABLE 503 - -/* error STATUS TEXT - * Send an error to the client with the given HTTP STATUS and TEXT. */ -void error(int status, const char *s) { - if (status < 100 || status > 999) - status = 500; - printf( - "Status: %03d\r\n" - "Content-Type: text/plain; charset=us-ascii\r\n" - "Content-Length: %u\r\n" - "\r\n" - "%s\n", - status, - strlen(s) + 1, - s); -} - -/* struct request - * Definition of a request we handle. */ -struct request { - char *r_tileset; - enum { - FN_GET_TILE = 0, - FN_GET_TILEIDS - } r_function; - - uint8_t r_tileid[TILEID_LEN]; - - int r_west, r_east, r_south, r_north; - enum { - F_RABX, - F_JSON, - F_TEXT, - F_HTML - } r_format; - - char *r_buf; -}; - -void request_free(struct request *R); - -/* request_parse PATHINFO - * Parse a request from PATHINFO. Returns a request on success or NULL on - * failure. */ -struct request *request_parse(const char *path_info) { - const char *p, *q; - struct request *R = NULL, Rz = {0}; - - /* Some trivial syntax checks. */ - if (!*path_info || *path_info == '/' || !strchr(path_info, '/')) { - err("PATH_INFO of \"%s\" is not a valid request", path_info); - return NULL; - } - - - /* - * TILESET/HASH - * TILESET/E,N/FORMAT - * TILESET/W-E,S-N/FORMAT - */ - - /* Tileset name consists of alphanumerics and hyphen. */ - p = path_info + strspn(path_info, - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "-"); - - if (*p != '/') - return NULL; - - R = xmalloc(sizeof *R); - *R = Rz; - R->r_buf = xmalloc(strlen(path_info) + 1); - R->r_tileset = R->r_buf; - - strncpy(R->r_tileset, path_info, p - path_info); - R->r_tileset[p - path_info] = 0; - - ++p; - - /* Mode. */ - if ((q = strchr(p, '/'))) { - /* Tile IDs request. */ - R->r_function = FN_GET_TILEIDS; - - /* Identify format requested. */ - ++q; - if (!strcmp(q, "RABX")) - R->r_format = F_RABX; - else if (!strcmp(q, "JSON")) - R->r_format = F_JSON; - else if (!strcmp(q, "text")) - R->r_format = F_TEXT; - else if (!strcmp(q, "html")) - R->r_format = F_HTML; - else { - err("request for unknown tile ID result format \"%s\"", q); - goto fail; - } - - if (4 == sscanf(p, "%d-%d,%d-%d", - &R->r_west, &R->r_east, &R->r_south, &R->r_north)) { - if (R->r_west < 0 || R->r_south < 0 - || R->r_east < R->r_west || R->r_north < R->r_south) { - err("area range query has invalid coordinates or order"); - goto fail; - } else - return R; - } else if (2 == sscanf(p, "%d,%d", &R->r_west, &R->r_south)) { - R->r_east = R->r_west; - R->r_north = R->r_south; - if (R->r_west < 0 || R->r_south < 0) { - err("tile ID query has negative coordinates"); - goto fail; - } else - return R; - } - } else { - size_t l; - - /* Tile request. */ - R->r_function = FN_GET_TILE; - if (strlen(p) != TILEID_LEN_B64) - goto fail; - - /* Decode it. Really this is "base64ish", so that we don't have to - * deal with '+' or '/' in the URL. */ - base64_decode(p, R->r_tileid, &l, 1); - if (l != TILEID_LEN) - goto fail; - - return R; - } - -fail: - request_free(R); - return NULL; -} - -/* request_free R - * Free storage allocated for R. */ -void request_free(struct request *R) { - if (!R) return; - xfree(R->r_buf); - xfree(R); -} - -void handle_request(void) { - char *path_info; - struct request *R; - static char *path; - static size_t pathlen; - size_t l; - tileset T; - time_t now; - struct tm *tm; - char date[32]; - const char *last_modified = "Wed, 20 Sep 2006 17:27:40 GMT"; - const unsigned cache_max_age = 365 * 86400; - - /* Date: header is required if we give a 304 Not Modified response. */ - time(&now); - tm = gmtime(&now); - strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S GMT", tm); - - /* All requests are given via PATH_INFO. */ - if (!(path_info = getenv("PATH_INFO"))) { - error(400, "No request path supplied"); - return; - } - - if ('/' == *path_info) - ++path_info; - - if (!(R = request_parse(path_info))) { - error(400, "Bad request"); - return; - } - - /* So we have a valid request. */ - l = strlen(R->r_tileset) + strlen(tiles_basedir) + 2; - if (pathlen < l) - path = xrealloc(path, pathlen = l); - sprintf(path, "%s/%s", tiles_basedir, R->r_tileset); - - if (!(T = tileset_open(path))) { - error(404, "Tileset not found"); - /* XXX assumption about the nature of the error */ - request_free(R); - return; - } - - /* XXX this is poor -- if the client sends If-Modified-Since: we just - * assume that it hasn't been. We might want to do something more clever at - * some point. */ - if (getenv("HTTP_IF_MODIFIED_SINCE")) { - printf( - "Status: 304 Not Modified\r\n" - "Date: %s\r\n" - "\r\n", date); - tileset_close(T); - request_free(R); - return; - } - - if (FN_GET_TILE == R->r_function) { - /* - * Send a single tile image to the client. - */ - void *buf; - size_t len; - - if ((buf = tileset_get_tile(T, R->r_tileid, &len))) { - printf( - "Content-Type: image/png\r\n" - "Content-Length: %u\r\n" - "Last-Modified: %s\r\n" - "Date: %s\r\n" - "Cache-Control: max-age=%u\r\n" - "\r\n", len, last_modified, date, cache_max_age); - fwrite(buf, 1, len, stdout); - xfree(buf); - } else - error(404, "Tile not found"); - /* XXX error assumption */ - } else if (FN_GET_TILEIDS == R->r_function) { - /* - * Send one or more tile IDs to the client, in some useful format. - */ - unsigned x, y; - static char *buf; - static size_t buflen, n; - unsigned rows, cols; - char *p; - - rows = R->r_north + 1 - R->r_south; - cols = R->r_east + 1 - R->r_west; - n = cols * rows; - if (buflen < n * TILEID_LEN_B64 + 256) - buf = xrealloc(buf, buflen = n * TILEID_LEN_B64 + 256); - - /* Send start of array in whatever format. */ - p = buf; - switch (R->r_format) { - case F_RABX: - /* Format as array of arrays. */ - *(p++) = 'L'; - p += netstring_write_int(p, (int)rows); - break; - - case F_JSON: - /* Ditto. */ - *(p++) = '['; - break; - - case F_TEXT: - /* Space and LF separated matrix so no special leader. */ - break; - - case F_HTML: - strcpy(p, - "<html><head><title>tileserver test</title></head><body>"); - p += strlen(p); - - if (R->r_west > 0) - p += sprintf(p, "<a href=\"../%u-%u,%u-%u/html\">west</a> ", - R->r_west - 1, R->r_east - 1, - R->r_south, R->r_north); - p += sprintf(p, "<a href=\"../%u-%u,%u-%u/html\">east</a> ", - R->r_west + 1, R->r_east + 1, - R->r_south, R->r_north); - - p += sprintf(p, "<a href=\"../%u-%u,%u-%u/html\">north</a> ", - R->r_west, R->r_east, - R->r_south + 1, R->r_north + 1); - if (R->r_south > 0) - p += sprintf(p, "<a href=\"../%u-%u,%u-%u/html\">south</a> ", - R->r_west, R->r_east, - R->r_south - 1, R->r_north - 1); - p += sprintf(p, "<br>"); - break; - } - - /* Iterate over tile IDs. */ - for (y = R->r_north; y >= R->r_south; --y) { - switch (R->r_format) { - case F_RABX: - *(p++) = 'L'; - p += netstring_write_int(p, (int)cols); - break; - - case F_JSON: - *(p++) = '['; - break; - - case F_TEXT: - break; /* nothing */ - - case F_HTML: - break; /* nothing */ - } - - for (x = R->r_west; x <= R->r_east; ++x) { - uint8_t id[TILEID_LEN]; - char idb64[TILEID_LEN_B64 + 1]; - bool isnull = 0; - - if (!(tileset_get_tileid(T, x, y, id))) - isnull = 1; - else - base64_encode(id, TILEID_LEN, idb64, 1, 1); - - if (p + 256 > buf + buflen) { - size_t n; - n = p - buf; - buf = xrealloc(buf, buflen *= 2); - p = buf + n; - } - - switch (R->r_format) { - case F_RABX: - if (isnull) - *(p++) = 'N'; - else { - *(p++) = 'T'; - p += netstring_write_string(p, idb64); - } - break; - - case F_JSON: - if (isnull) { - strcpy(p, "null"); - p += 4; - } else { - *(p++) = '"'; - strcpy(p, idb64); - p += TILEID_LEN_B64; - *(p++) = '"'; - } - if (x < R->r_east) - *(p++) = ','; - break; - - case F_TEXT: - if (isnull) - *(p++) = '-'; - else { - strcpy(p, idb64); - p += TILEID_LEN_B64; - } - if (x < R->r_east) - *(p++) = ' '; - break; - - case F_HTML: - if (isnull) - ; /* not much we can do without the tile sizes */ - else - p += sprintf(p, - "<img title=\"%u,%u\" src=\"../%s\">", - x, y, - idb64); - break; - } - } - - switch (R->r_format) { - case F_RABX: - break; /* no row terminator */ - - case F_JSON: - *(p++) = ']'; - if (y < R->r_north) - *(p++) = ','; - break; - - case F_TEXT: - *(p++) = '\n'; - break; - - case F_HTML: - p += sprintf(p, "<br>"); - break; - } - } - - /* Array terminator. */ - switch (R->r_format) { - case F_RABX: - break; - - case F_JSON: - *(p++) = ']'; - break; - - case F_TEXT: - break; - - case F_HTML: - p += sprintf(p, "</body></html>"); - } - /* NB no terminating NUL */ - - /* Actually send it. */ - printf("Content-Type: "); - switch (R->r_format) { - case F_RABX: - printf("application/octet-stream"); - break; - - case F_JSON: - /* Not really clear what CT to use here but Yahoo use - * "text/javascript" and presumably they've done more testing - * than us.... */ - printf("text/javascript"); - break; - - case F_TEXT: - printf("text/plain; charset=us-ascii"); - break; - - case F_HTML: - printf("text/html; charset=us-ascii"); - break; - } - printf("\r\n" - "Content-Length: %u\r\n" - "Last-Modified: %s\r\n" - "Date: %s\r\n" - "Cache-Control: max-age=%u\r\n" - "\r\n", - (unsigned)(p - buf), last_modified, date, cache_max_age); - - fwrite(buf, 1, p - buf, stdout); - } - - tileset_close(T); - request_free(R); -} - -int main(int argc, char *argv[]) { - struct stat st; - bool initialised = 0; - -#ifndef NO_FASTCGI - while (FCGI_Accept() >= 0) -#endif - { - /* Stupid order since with fcgi_stdio if we haven't called FCGI_Accept - * we don't get any stderr output.... */ - if (!initialised) { - if (argc != 2) - die("single argument is path to tile sets"); - tiles_basedir = argv[1]; - if (-1 == stat(tiles_basedir, &st)) - die("%s: stat: %s", tiles_basedir, strerror(errno)); - else if (!S_ISDIR(st.st_mode)) - die("%s: Not a directory", tiles_basedir); - initialised = 1; - } - - handle_request(); - } - - return 0; -} diff --git a/tileserver/tileset.c b/tileserver/tileset.c deleted file mode 100644 index 866125e30..000000000 --- a/tileserver/tileset.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * tileset.c: - * Interface to an individual tile set. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: tileset.c,v 1.6 2006-09-20 16:44:53 chris Exp $"; - -/* - * Tile sets are stored in directory trees which contain indices of tile - * locations to tile IDs, packed archives of tile images, and indices of where - * each tile image lives in the corresponding packed file. Tile IDs are SHA1 - * digests of the tile contents, enabling efficient storage in the presence of - * repeated tiles. - * - * Note that this doesn't have attractive properties for locality of reference. - * That will need fixing if performance under the current scheme is not - * acceptable. - */ - -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "cdb.h" -#include "tileset.h" -#include "util.h" - -#define TILEID_LEN 20 - -struct tileset { - char *t_path, *t_pathbuf; - cdb t_tileid_idx; - /* Tile IDs are stored in the tile ID database in N-by-N square blocks, - * so that the tile ID for (X, Y) is obtained by getting the block for - * (X / N, Y / N) and looking up (X, Y) within it. The blocking factor is - * (hopefully!) chosen to be about a disk block in size. It is stored in - * the tile ID database. Rather than allocating a specific tile ID for a - * tile which is not present, each information block is preceded by a - * coverage bitmap. */ - unsigned t_blocking; - bool t_first; - unsigned t_x, t_y; - cdb_datum t_block; - /* File pointer open on an image file and the name of the file it's open - * on. */ - char *t_imgfile; - FILE *t_fpimg; -}; - - -/* tileset_open PATH - * Open the tileset at PATH, returning a tileset object on success or NULL on - * failure. */ -tileset tileset_open(const char *path) { - struct tileset *T, Tz = {0}; - cdb_datum d = NULL; - char *s; - int i; - - T = xmalloc(sizeof *T); - *T = Tz; - - T->t_first = 1; - T->t_path = xstrdup(path); - T->t_pathbuf = xmalloc(strlen(path) + sizeof "/tiles/a/b/c/tiles.cdb"); - - /* Open the tile ID index. */ - sprintf(T->t_pathbuf, "%s/index.cdb", T->t_path); - if (!(T->t_tileid_idx = cdb_open(T->t_pathbuf))) - goto fail; - - /* get blocking factor */ - if (!(d = cdb_get_string(T->t_tileid_idx, "blocking"))) - goto fail; - s = (char*)d->cd_buf; - if (!(i = atoi(s)) || i < 0) - goto fail; - T->t_blocking = (unsigned)i; - - cdb_datum_free(d); - - return T; - -fail: - tileset_close(T); - if (d) cdb_datum_free(d); - return NULL; -} - -/* tileset_close T - * Free resources associated with T. */ -void tileset_close(tileset T) { - if (!T) return; - cdb_close(T->t_tileid_idx); - xfree(T->t_path); - xfree(T->t_pathbuf); - xfree(T); -} - -/* tileset_path T - * Return the path used to open T. */ -char *tileset_path(tileset T) { - return T->t_path; -} - -static size_t blockmap_bitmap_len(const unsigned blocking) { - return (blocking * blocking + 8) / 8; -} - -static size_t blockmap_len(const unsigned blocking) { - size_t l; - /* Bitmap of null tiles. */ - l = blockmap_bitmap_len(blocking); - /* Tile IDs themselves */ - l += blocking * blocking * TILEID_LEN; - return l; -} - -/* tileset_get_tileid T X Y ID - * Write into ID the tile ID of the tile at (X, Y) in T, returning true on - * success or false on failure. */ -bool tileset_get_tileid(tileset T, const unsigned x, const unsigned y, - uint8_t *id) { - unsigned x2, y2, off, off0; - uint8_t *b; - - if (T->t_first || T->t_x != x || T->t_y != y) { - /* Grab block from database. */ - char buf[32]; - - T->t_first = 0; - if (T->t_block) cdb_datum_free(T->t_block); - - sprintf(buf, "%u,%u", x / T->t_blocking, y / T->t_blocking); - if (!(T->t_block = cdb_get_string(T->t_tileid_idx, buf))) - return 0; - } - - if (T->t_block->cd_len != blockmap_len(T->t_blocking)) - return 0; - /* XXX also report bogus ID block */ - - b = (uint8_t*)T->t_block->cd_buf; - - x2 = x % T->t_blocking; - y2 = y % T->t_blocking; - off = (x2 + y2 * T->t_blocking); - - /* For a tile not present the corresponding bit in the bitmap is set. */ - if (b[off >> 3] & (1 << (off & 7))) - return 0; - - off0 = blockmap_bitmap_len(T->t_blocking); - memcpy(id, b + off0 + off * TILEID_LEN, TILEID_LEN); - - return 1; -} - -/* tileset_get_tile T ID LEN - * Retrieve the tile identified by ID, writing its length into *LEN and - * returning a malloced buffer containing its contents on success, or returning - * NULL on failure. */ -void *tileset_get_tile(tileset T, const uint8_t *id, size_t *len) { - cdb idx = NULL; - cdb_datum d = NULL; - void *ret = NULL; - unsigned off; - FILE *fp = NULL; - - sprintf(T->t_pathbuf, "%s/tiles/%x/%x/%x/tiles.cdb", - T->t_path, (unsigned)(id[0] >> 4), - (unsigned)(id[0] & 0xf), (unsigned)(id[1] >> 4)); - if (!(idx = cdb_open(T->t_pathbuf))) - return NULL; - /* also maybe report bogus index */ - - if (!(d = cdb_get_buf(idx, id, TILEID_LEN))) - goto fail; - - if (2 != sscanf((char*)d->cd_buf, "%x:%x", &off, len)) - goto fail; - - sprintf(T->t_pathbuf, "%s/tiles/%x/%x/%x/tiles", - T->t_path, (unsigned)(id[0] >> 4), - (unsigned)(id[0] & 0xf), (unsigned)(id[1] >> 4)); - if (!(fp = fopen(T->t_pathbuf, "rb"))) - goto fail; - else if (-1 == fseek(fp, off, SEEK_SET)) - goto fail; - - ret = xmalloc(*len); - if (*len != fread(ret, 1, *len, fp)) { - xfree(ret); - goto fail; - } - -fail: - if (idx) cdb_close(idx); - if (d) cdb_datum_free(d); - if (fp) fclose(fp); - return ret; -} diff --git a/tileserver/tileset.h b/tileserver/tileset.h deleted file mode 100644 index 58b22a5e2..000000000 --- a/tileserver/tileset.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * tileset.h: - * Interface to an individual tile set. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - * $Id: tileset.h,v 1.2 2006-09-20 13:24:58 chris Exp $ - * - */ - -#ifndef __TILESET_H_ /* include guard */ -#define __TILESET_H_ - -#include <sys/types.h> - -#include <stdbool.h> -#include <stdint.h> - -#define TILEID_LEN 20 -#define TILEID_LEN_B64 27 - -typedef struct tileset *tileset; - -/* tileset.c */ -tileset tileset_open(const char *path); -void tileset_close(tileset T); -bool tileset_get_tileid(tileset T, const unsigned x, const unsigned y, - uint8_t *id); -void *tileset_get_tile(tileset T, const uint8_t *id, size_t *len); - -#endif /* __TILESET_H_ */ diff --git a/tileserver/util.c b/tileserver/util.c deleted file mode 100644 index fbc500438..000000000 --- a/tileserver/util.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * util.c: - * Miscellaneous utility functions. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - */ - -static const char rcsid[] = "$Id: util.c,v 1.1 2006-09-20 15:45:51 chris Exp $"; - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "util.h" - -/* - * Wrappers for memory-allocation functions. - */ -void *xmalloc(const size_t s) { - void *v; - if (!(v = malloc(s))) - die("malloc(%u bytes): %s", (unsigned)s, strerror(errno)); - return v; -} - -void *xcalloc(const size_t a, const size_t b) { - void *v; - if (!(v = calloc(a, b))) - die("calloc(%u * %u bytes): %s", - (unsigned)a, (unsigned)b, strerror(errno)); - return v; -} - -void *xrealloc(void *b, const size_t s) { - void *v; - if (!(v = realloc(b, s))) - die("realloc(%u bytes): %s", (unsigned)s, strerror(errno)); - return v; -} - -char *xstrdup(const char *s) { - char *t; - if (!(t = strdup(s))) - die("strdup(%u bytes): %s", (unsigned)(strlen(s) + 1), strerror(errno)); - return t; -} - -void xfree(void *v) { - if (v) free(v); -} diff --git a/tileserver/util.h b/tileserver/util.h deleted file mode 100644 index a2f1f7bc8..000000000 --- a/tileserver/util.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * util.h: - * Utilities. - * - * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved. - * Email: chris@mysociety.org; WWW: http://www.mysociety.org/ - * - * $Id: util.h,v 1.1 2006-09-20 15:45:51 chris Exp $ - * - */ - -#ifndef __UTIL_H_ /* include guard */ -#define __UTIL_H_ - -/* err FORMAT [ARG ...] - * Write an error message to standard error. */ - /* XXX format this with a timestamp for the error-log? */ -#define err(...) \ - do { \ - fprintf(stderr, "tileserver: "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) - -/* die FORMAT [ARG ...] - * Write an error message to standard error and exit unsuccessfully. */ -#define die(...) do { err(__VA_ARGS__); exit(1); } while (0) - -/* util.c */ -void *xmalloc(const size_t s); -void *xcalloc(const size_t a, const size_t b); -void *xrealloc(void *b, const size_t s); -char *xstrdup(const char *s); -void xfree(void *v); - -#endif /* __UTIL_H_ */ |