Jim's Depository

this code is not yet written
 

I’m entering into Virtual Hosting. Many of the pages and services I’ve run out of spare gear at FSG are now moved or moving to a virtual host. I chose VPSLink for their \$7/mo tiny server, then while moving my domain name pointers discovered that Gandi.net is starting a virtual hosting business with more RAM and disk for about the same price.

Living in a tiny slice of a virtual host is certainly different from living in a leftover 2GHz/1GB/500GB PC, fortunately I remember when 64M of RAM was huge so I think I’ll get by just fine.

  • postfix/dovecot in TLS, lighttpd+fastcgi+php5 all fits nicely.
  • TLS incurs a fair bit of RAM use.
  • CPU use is odd. I don’t have visibility to know if I am CPU bound. I don’t think I am, but I can’t see from inside my Xen box.
  • The machine is noticeably sluggish at things like an initial rsync backup offsite. Slow disk? Slow network? I’m not sure.
  • The total annual bill will cost less than the electricity to keep a PC powered up. Happy Earth Day!

I guess if you find the site gone and just this at archive.org you’ll know I’ve had a virtual hosting disaster.  I don’t foresee a problem. I don’t know what the hosting company does if a machine fails, but I keep a full backup every night in my closet so I’ll be ok. 

VHD is a nice format for exchanging disks. It doesn’t take up a lot of bytes with the empty space of a filesystem. But if you want to access the data without a virtual machine it can be a bit of a pain.

The attached program will expand most VHD files into a sparse image of the disk. You can then do whatever you wish with that, such as:

  • Use it with a virtual machine that takes raw images.
  • Convert it to a VMDK with qemu-img from the qemu folk. (Note: some people say qemu-img can read VHD format already. It didn’t work for me and the man page doesn’t mention it.)
  • Mount it on your Linux box, though you have to skip some bytes to get over the MBR and down to the first partition. Go look at Creating and using disk images mini-howto | Marc’s Realm and find “The dirty way”. Basically it boils down to trying all the plausible offsets until you find the right one:

    for ((i=0 ; $i < 10000 ; i=$i + 1)) ; do    mount -o loop,offset=$(($i * 512)) image.bootable /mnt && breakdone
    
  • Give it a sound grepping.

Note: I don’t support this program or even intend to run it ever again. I converted my data. If you need to convert your data, then enjoy.

Attachments

vhd2img.tar.gz 4515 bytes
Hello Jim,

This looks like just what the doctor ordered but it failed to compile for me:

Did I do something wrong or do you have any suggestions to help fix it?

Thanks much,
Patrick -> patrickkirchner   AT yahoo . com

Current Directory = /tmp/vhd2img
-->make
cc -g -MMD -Wstrict-prototypes -Wall -Werror    vhd2img.c   -o vhd2img
vhd2img.c:38:38: error: missing terminating ' character
vhd2img.c:59:37: error: missing terminating ' character
vhd2img.c:67:67: error: missing terminating ' character
make: *** [vhd2img] Error 1

Hello again,

Well, err, uh, duh!  I poked around in the .c file and removed the whole license preamble then it compiled just fine.  Sorry about that.  It worked great but the resulting .raw and converted .qcow2 file give me a BSOD when run with kvm.

Thanks,
Patrick.
I've fixed the compilation error and made it 64bit aware, I still need to test it anyway here the download link:

http://oss.netfarm.it/download/vhd2img-64bit-aware.tar.bz2

(use wget or direct link, a link from a page will cause 503)

the bsod is probably a 7b error (inaccessible boot device, you may need to fix system registry hive to use standard ide but it's tricky and you need a windows box or of course a working windows vm :D)

contact me if interested (sherpya@netfarm.it)

I successfully started a converted windows vista evaulation vhd I'm still fighting with the mouse and vnc :)
Well, it's been two years since anyone touched this, so no one probably cares, but I needed to do this in Windows, so I ported it.  Here it is for anyone that cares.  It should still be Linux compatible.  I built and used it with Visual Studio 2010 as an x64 app, but it should work fine any way that you build it.  Oh, and it's cpp now.

[code]
#ifdef WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <winsock.h>
#include <stdint.h>
#define off_t uint64_t
#define fseeko _fseeki64
#define ftello _ftelli64 
#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
#else
/*
** some defines to make Linux happy with big files
*/
#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE /* enable fseeko */
#include <arpa/inet.h>
#include <inttypes.h>
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#endif
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

static int verbose = 1;

typedef struct {
uint16_t cylinders;
unsigned char heads;
unsigned char sectors;
} VHD_GEOMETRY;

PACK(
struct VHD_FOOTER{
char cookie[8];
uint32_t features;
uint32_t version;
uint64_t dataOffset;
uint32_t timeStamp;
char creatorApplication[4];
uint32_t creatorVersion;
char creatorOS[4];
uint64_t originalSize;
uint64_t currentSize;
VHD_GEOMETRY diskGeometry;
uint32_t diskType;
uint32_t checksum;
unsigned char uniqueId[16];
unsigned char savedState;
unsigned char padding[427];
});

PACK(
typedef struct{
char cookie[8];
uint64_t dataOffset;
uint64_t tableOffset;
uint32_t headerVersion;
uint32_t maxTableEntries;
uint32_t blockSize;
uint32_t checksum;
unsigned char parentUniqueId[16];
uint32_t parentTimeStamp;
uint32_t reserved1;
unsigned char parentUnicodeName[512];
PACK(
struct{
unsigned char platformCode[4];
uint32_t platformDataSpace;
uint32_t platformDataLength;
uint32_t reserved;
uint64_t platformDataOffset;
}) partentLocator[8];
unsigned char reserved2[256];
}) VHD_DYNAMIC;

/*
** network byte order to host, "double" i.e. long long
*/
static uint64_t ntoh64( uint64_t v)
{
if (htons(1) == 1) return v;
else {
return(uint64_t)ntohl( v&0x00000000ffffffff) << 32 | 
(uint64_t)ntohl( (v>>32)&0x00000000ffffffff);
}
}

static void dump_footer(VHD_FOOTER *footer)
{
int i;

fprintf(stderr,"==================== VHD Footer =====================\n");
fprintf(stderr,"%24s : '%8.8s'\n", "cookie", footer->cookie);
fprintf(stderr,"%24s : %08x\n", "features", ntohl(footer->features));
fprintf(stderr,"%24s : %08x\n", "version", ntohl(footer->version));
fprintf(stderr,"%24s : %016lx\n", "data offset", ntoh64(footer->dataOffset));
fprintf(stderr,"%24s : %08x\n", "time stamp", ntohl(footer->timeStamp));
fprintf(stderr,"%24s : '%4.4s'\n", "creator application", footer->creatorApplication);
fprintf(stderr,"%24s : %08x\n", "creator version", ntohl(footer->creatorVersion));
fprintf(stderr,"%24s : '%4.4s'\n", "creator os", footer->creatorOS);
fprintf(stderr,"%24s : %016lx\n", "original size", ntoh64(footer->originalSize));
fprintf(stderr,"%24s : %016lx\n", "current size", ntoh64(footer->currentSize));
fprintf(stderr,"%24s : %d\n", "cylinders", ntohs(footer->diskGeometry.cylinders));
fprintf(stderr,"%24s : %d\n", "heads", footer->diskGeometry.heads);
fprintf(stderr,"%24s : %d\n", "sectors", footer->diskGeometry.sectors);
fprintf(stderr,"%24s : %08x\n", "disk type", ntohl(footer->diskType));
fprintf(stderr,"%24s : %08x\n", "disk type", ntohl(footer->checksum));
fprintf(stderr,"%24s : ", "unique id");
for (i = 0; i < 16; i++) {
fprintf(stderr,"%02x%s", footer->uniqueId[i], "...-.-.-.-......"[i] == '-' ? "-" : "");
}
fprintf(stderr,"\n");
fprintf(stderr,"%24s : %02x\n", "saved state", footer->savedState);
/* should add the parent locator entries if I ever see a differenced file */
}

static void dump_dynamic(VHD_DYNAMIC *dynamic)
{
int i;

fprintf(stderr,"==================== VHD Dynamic =====================\n");
fprintf(stderr,"%24s : '%8.8s'\n", "cookie", dynamic->cookie);
fprintf(stderr,"%24s : %016lx\n", "data offset", ntoh64(dynamic->dataOffset));
fprintf(stderr,"%24s : %016lx\n", "table offset", ntoh64(dynamic->tableOffset));
fprintf(stderr,"%24s : %08x\n", "version", ntohl(dynamic->headerVersion));
fprintf(stderr,"%24s : %d\n", "max table entries", ntohl(dynamic->maxTableEntries));
fprintf(stderr,"%24s : %d\n", "block size", ntohl(dynamic->blockSize));
fprintf(stderr,"%24s : %08x\n", "checksum", ntohl(dynamic->checksum));
fprintf(stderr,"%24s : ", "parent unique id");
for (i = 0; i < 16; i++) {
fprintf(stderr,"%02x%s", dynamic->parentUniqueId[i], "...-.-.-.-......"[i] == '-' ? "-" : "");
}
fprintf(stderr,"\n");
fprintf(stderr,"%24s : %08x\n", "parent time stamp", ntohl(dynamic->parentTimeStamp));
fprintf(stderr,"%24s : %08x\n", "reserved", ntohl(dynamic->reserved1));
fprintf(stderr,"%24s : ", "parent unicode name");
for (i = 0; i < 512; i++) {
fprintf(stderr,"%02x %s", dynamic->parentUniqueId[i], (i%32)==31 ? "\n" : "");
}

}

uint32_t checksum(void *buffer, int nBytes) {
uint32_t checksum=0;
uint8_t *bytes = (uint8_t *)buffer;
int counter;
for(counter = 0; counter < nBytes; counter++) {
checksum += bytes[counter];
}
return ~checksum;
}

off_t get_footer(FILE *file, VHD_FOOTER *footer) {
uint64_t footerOffset = 0;
VHD_FOOTER returnFooter;
bool passedChecksum = false;

// First, try the footer at the end of the file.
if (fseeko(file, -512, SEEK_END) != 0) {
fprintf(stderr,"Failed to seek to the footer: %s\n", strerror(errno));
exit(1);
}
footerOffset = ftello(file);
if ( fread( &returnFooter, sizeof(returnFooter), 1, file) != 1) {
fprintf(stderr,"Failed to read footer of input file: %s\n", strerror(errno));
exit(1);
}
// Now, calculate the check-sum.
uint32_t uiChecksum = returnFooter.checksum;
returnFooter.checksum = 0;
uint32_t ourChecksum = checksum(&returnFooter, sizeof(returnFooter));
passedChecksum = uiChecksum == htonl(ourChecksum);
returnFooter.checksum = uiChecksum;
// If the first footer didn't pass the checksum, try the one at the end.
if(!passedChecksum) {
fprintf(stderr, "Checksum for footer failed, trying backup copy.\n");
if(fseeko(file, 0, SEEK_SET) != 0) {
fprintf(stderr,"Failed to backup footer of input file: %s\n", strerror(errno));
exit(1);
}
if ( fread( &returnFooter, sizeof(returnFooter), 1, file) != 1) {
fprintf(stderr,"Failed to read footer of input file: %s\n", strerror(errno));
exit(1);
}
// Now, calculate the check-sum.
uint32_t uiChecksum = returnFooter.checksum;
returnFooter.checksum = 0;
uint32_t ourChecksum = checksum(&returnFooter, sizeof(returnFooter));
passedChecksum = uiChecksum == htonl(ourChecksum);
returnFooter.checksum = uiChecksum;
}

if(passedChecksum) {
memcpy(footer, &returnFooter, sizeof(returnFooter));
} else {
fprintf(stderr,"Both the header and footer failed the checksum.\n");
exit(1);
}

return footerOffset;
}

int main( int argc, char **argv)
{
/*
** Make sure we were compiled correctly.
*/
assert( sizeof(VHD_FOOTER)==512);
assert( sizeof(VHD_DYNAMIC)==1024);

if (argc != 3) {
fprintf(stderr,"Usage: vhd2img INPUTFILE OUTPUTFILE\n");
exit(1);
}

FILE *in = fopen(argv[1],"rb");
if (!in) {
fprintf(stderr,"Failed to open input file '%s': %s\n", argv[1], strerror(errno));
exit(1);
}

FILE *out = fopen(argv[2],"wb");
if (!out) {
fprintf(stderr,"Failed to open output file '%s': %s\n", argv[1], strerror(errno));
exit(1);
}

VHD_FOOTER footer;
off_t footerOffset = get_footer(in, &footer);

if (verbose) dump_footer(&footer);

switch (htonl(footer.diskType)) {
case 4:
fprintf(stderr,"Differencing VHDs not supported.\n");
exit(1);
case 2:
/*
** Warning: untested code. I don't have one of these files.
*/
if (verbose) fprintf(stderr,"Processing fixed VHD...\n");
{
off_t o = 0;
char buf[512];

if (fseeko(in,(off_t)0,SEEK_SET) != 0) {
fprintf(stderr,"Failed to rewind input file: %s\n", strerror(errno));
exit(1);
}
for (o = 0; o < footerOffset; o += 512) {
if (fread( buf, 512, 1, in) != 1) {
fprintf(stderr,"Failed to read input file: %s\n", strerror(errno));
exit(1);
}
if (fwrite( buf, 512, 1, out) != 1) {
fprintf(stderr,"Failed to write output file: %s\n", strerror(errno));
exit(1);
}
}
fprintf(stderr,"Completed. %lld bytes written.\n", (long long)footerOffset);
return 0;
}
case 3:
if (verbose) fprintf(stderr,"Processing dynamic VHD...\n");
break;
default:
fprintf(stderr,"Disk type %08x not recognized.\n", htonl(footer.diskType));
exit(1);
}

VHD_DYNAMIC dynamic;

if (fseeko(in,ntoh64(footer.dataOffset),SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to dynamic disk header: %s\n", strerror(errno));
exit(1);
}

if (fread( &dynamic, sizeof(dynamic), 1, in) != 1) {
fprintf(stderr,"Failed ot read dynamic disk header: %s\n", strerror(errno));
exit(1);
}

if (verbose) dump_dynamic( &dynamic);

uint32_t blockBitmapSectorCount = (ntohl(dynamic.blockSize)/512/8+511)/512;
uint32_t sectorsPerBlock = ntohl(dynamic.blockSize)/512;
uint8_t *bitmap = (uint8_t *)malloc(blockBitmapSectorCount*512);

if (verbose) fprintf(stderr, "block bitmap sector count is %d\n", blockBitmapSectorCount);

if (fseeko(in,ntoh64(dynamic.tableOffset),SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to block allocation table: %s\n", strerror(errno));
exit(1);
}

uint32_t bats = ntohl( dynamic.maxTableEntries);
uint32_t *bat = (uint32_t *)calloc( sizeof(*bat) , bats);
if (fread( bat, sizeof(*bat), bats, in) != bats) {
fprintf(stderr,"Failed to read block allocation table: %s\n", strerror(errno));
exit(1);
}

if (fseeko(out, bats * sectorsPerBlock * 512 - 1,SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to the end of the image: %s\n", strerror(errno));
exit(1);
}
if (fwrite("", 1, 1, out) != 1) {
fprintf(stderr,"Failed to write the last byte of image: %s\n", strerror(errno));
exit(1);
}

unsigned int b;
uint32_t emptySectors = 0;
uint32_t usedSectors = 0;
uint32_t usedZeroes = 0;
char buf[512];

for (b = 0; b < bats; b++) {
if (ntohl(bat[b]) == 0xffffffff) {
emptySectors += sectorsPerBlock;
continue;   /* totally empty block */
}

uint64_t bo = ntohl(bat[b])*512LL;

if (bo > footerOffset) {
fprintf(stderr,"Bad block offset\n");
exit(1);
}
if (fseeko(in, bo, SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to data block bitmap: %s\n", strerror(errno));
exit(1);
}
if (fread( bitmap, 512*blockBitmapSectorCount, 1, in) != 1) {
fprintf(stderr,"Failed to read block bitmap(%ld): %s\n", bo, strerror(errno));
exit(1);
}

unsigned int s,k;
uint64_t opos = 0xffffffffffffffffLL;

if (fseeko(in, bo+512*blockBitmapSectorCount, SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to input sectors: %s\n", strerror(errno));
exit(1);
}
for (s = 0; s < sectorsPerBlock; s++) {
if (fread( buf, 512, 1, in) != 1) {
fprintf(stderr,"Failed to read sector: %s\n", strerror(errno));
exit(1);
}

int empty = 1;
for (k = 0; k < 512; k++) {
if (buf[k]) {
empty = 0;
break;
}
}

if ((bitmap[s/8] & (1<<(7-s%8))) == 0) {
emptySectors++;
if (!empty) {
fprintf(stderr,"block %d, sector %d should be empty and isn't.\n", b, s);
}
} else {
usedSectors++;
if (empty) {
usedZeroes++;
} else {
uint64_t pos = (b*sectorsPerBlock + s) * 512LL;
if (pos != opos) {
if (fseeko( out, pos, SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek output file for sector %lld (block %d of %u,sector %d of %d): %s\n", 
(b*sectorsPerBlock + s) * 512LL, b,bats,s,sectorsPerBlock,
strerror(errno));
exit(1);
}
opos = pos;
}
if (fwrite( buf, 512, 1, out) != 1) {
fprintf(stderr,"Failed to write sector: %s\n", strerror(errno));
exit(1);
}
opos += 512LL;
}
}
}
}

if (verbose) {
fprintf(stderr,"%4.1f%% of the sectors were used.\n", 
100.0*usedSectors/(usedSectors+emptySectors));
fprintf(stderr,"%4.1f%% of the sectors are used after removing zeroes.\n", 
100.0*(usedSectors-usedZeroes)/(usedSectors+emptySectors));
}

return 0;
}
[/code]
Basically it boils down to trying all the plausible offsets until you find the right one
Wow! It is not needed! If you have cylinder-aligned partition, just skip 63*512 bytes. If you have megabyte-aligned, 1024*2*512.
I just built qemu from the source for OS X 10.7 on Mac. I had to use these flags to get the make to complete without an error on compiling darwin-user:
>>  ./configure --disable-darwin-user --disable-sdl --enable-cocoa --disable-kvm --disable-bsd-user

Then i directly converted a Windows 7 x64 created VHD image into a raw... well it's not done yet, but seems to be running fine. The latest qemu-img does understand the source file is .vhd and so the command I had to use is:
>>  qemu-img convert -O raw source.vhd output.raw

next step is to use dd to lay down the raw image onto a hdd partition... 
I haven't actually mounted the resulting image file yet, but the conversion seemed to run OK. If you want to check your output image file contents, for information on what is actually in there, you can use the "disktype" package
from sourceforge (disktype.sourceforge.net). That program is also included in your favorite package manager. It'll make it a bit easier to figure out where your
partitions are located. This is a sample output, using my newly converted image file. "disktype myimage.img"

Regular file, size 31.00 GiB (33286000640 bytes)
DOS/MBR partition map
Partition 1: 29.31 GiB (31473008640 bytes, 61470720 sectors from 2048, bootable)
  Type 0x83 (Linux)
  Ext3 file system
    UUID 09DE2E1E-2C2F-4378-A8E6-59C8723865C7 (DCE, v4)
    Volume size 29.31 GiB (31473008640 bytes, 7683840 blocks of 4 KiB)
Partition 2: 2.687 GiB (2884632576 bytes, 5634048 sectors from 61472768)
  Type 0x82 (Linux swap / Solaris)
  Linux swap, version 2, subversion 1, 4 KiB pages, little-endian
    Swap size 2.687 GiB (2884624384 bytes, 704254 pages of 4 KiB)

Sometimes you will find data structures to read that are encoded little-endian. You may find these functions useful. They aren’t as nightmarish to read as macros and presumably your compiler will do right by them.

#include <stdio.h>
#include <arpa/inet.h>/*
** Little endian to host, short
*/
static unsigned short ltohs( unsigned short v)
{
    if ( htons(1) == 1) {
    return ((v>>8)&0xff) | ((v<<8)&0xff00);
    } else return v;
}/*
** Little endian to host, Long
*/
static unsigned long ltohl( unsigned long v)
{
    if ( htons(1) == 1) {
    return ((v>>24)&0xff) | ((v>>8)&0xff00) | 
               ((v<<8)&0xff0000) | ((v << 24)&0xff000000);
    } else return v;
}/*
** Little endian to host, "double" i.e. long long
*/
static unsigned long long ltohd( unsigned long long v)
{
    if ( htons(1) == 1) {
    return (unsigned long long)ltohl( v&0x00000000ffffffff) << 32 | 
        (unsigned long long)ltohl( (v>>32)&0x00000000ffffffff);
    } else return v;
}int main( int argc, char **argv)
{
    unsigned char t1[] = { 0x01, 0x02, 0x03, 0x04, 
                           0x05, 0x06, 0x07, 0x08 };
    unsigned char t2[] = { 0xff, 0xfe, 0xfd, 0xfc, 
                           0xfb, 0xfa, 0xf9, 0xf8 };    printf("ltohs(01,02) = %04x\n", ltohs(*(unsigned short *)t1));
    printf("ltohs(ff,fe) = %04x\n", ltohs(*(unsigned short *)t2));
    printf("ltohl(01,02,03,04) = %08x\n", ltohl(*(unsigned long *)t1));
    printf("ltohl(ff,fe,fd,fc) = %08x\n", ltohl(*(unsigned long *)t2));
    printf("ltohd(01,02,03,04,05,06,07,08) = %016llx\n", 
           ltohd(*(unsigned long long *)t1));
    printf("ltohd(ff,fe,fd,fc,fb,fa,f9,f8) = %016llx\n", 
           ltohd(*(unsigned long long *)t2));
}

Temperature is the enemy of hard disks. Hot disks fail sooner.

If you have a well designed server with lightly loaded disks in a cool room your drives are probably running around 25°C(77°F). If you have a 4U box that you chucked full of drives you may find some of your drives running much hotter. For instance, I just found two running at 41°C(105°F) in a box with otherwise cool drives.

Drives are typically specified to operate up to 55°C(131°F), but their lives are shortened. Estimating from Google data it looks like your failure rate within three years is about triple for 45°C versus 25°C. In the third year, about 1 in 6 of the hot drives will fail.

So protect your servers:

  1. Know the problem: In debian land, install hddtemp and run it to see which drives are hot. Windows users might use DTemp.

    `` vev# for v in a b c d ;do hddtemp /dev/sd\$v ;done /dev/sda: ST3750640AS: 34°C /dev/sdb: ST3750640AS: 27°C /dev/sdc: ST3750640AS: 41°C /dev/sdd: ST3750640AS: 40°C vev# # I have two hot drives.

  2. Mitigate: If you have hot drives, move them around or adjust airflow, perhaps by making little cardstock air dams inside the server to cool the drives. Add a fan blowing on them if you have a large case.

  3. Monitor: Record their temperatures and check in on them once in a while to make sure things aren’t going badly.

Debian users might also want to install smartmontools which will track your S.M.A.R.T. data and notify you of problems.

Note: for SATA drives you will need a “-d ata” or it will misaddress them as SCSI drives and you need a “-m foo@example.com” if you want to be email notified.

Somewhere along the line the GNU C library and the BSD folks added variants of sprintf() that malloc() the resulting string. Very handy for avoiding an overflow situation and also makes for cleaner reading code.

`` int asprintf( char **ret, const char *format, …); int vasprintf( char **ret, const char *format, va_list ap);

Before:

`` char buf[32]; sprintf(buf, “s:%d m:%s”, code, msg); // stack overflow when msg is too long

… or …

``

char buf[1024];
snprintf( buf, sizeof(buf)-1, “s:%d m:%s”, code, msg); // fail oddly if we guessed wrong on the maximum buf size

After:

``

char *buf;
asprintf( &buf, “s:%d m:%s”, code, msg);
… free(buf);

Danger danger DANGER: This will not work on the tiny libc in OpenWRT and similar embedded systems. But at least you will get a link error during the build.

Now if only we had a scanf() that would do a realloc() on its pointers.

Oh look, there is a similar function for scanf().  You can do something like...

char *adj = 0;
sscanf(somestuff,"Some %as stuff", &adj);

... but only if you are using GNU libc. I got burned when I used this in a daemon and then moved it to OpenWRT where they uses a different libc.

Femtoblogger has just been rolled out to a corporate community of 150 users. Let the bug reports and feature requests roll.

Perhaps it isn’t fair, firefox 2 is the oldest of the browsers I am supporting, but could they make the editable html interface any less user friendly? 

The editable region has to be in an IFRAME, I’d rather not restrict people to a fixed region of screen, but ok. Now the user clicks in the IFRAME to edit, but it won’t place the insertion point unless they happen to click on an element, so if you click in the bottom of the IFRAME you get no insertion point. That makes me create a special onfocus handler to place the insertion point someplace to get them started. Then in one final gesture of hostility, the insertion point placing code doesn’t work on an empty IFRAME, so I have to insert a nonbreaking space to open up a line for the insertion point (because a normal space gets an insertion point about 2 pixels tall.) All told, most of a day wasted to support  bad decisions from the firefox coders. They should have tried to use it after they coded it.

I recently needed to install 10 motion detecting cameras on 5 doorways. My first thought was to use ethernet cameras, but at \$300 each that gets expensive. My second plan was to use Linksys WRTSL54GS units running OpenWrt and cheap web cameras with the ‘motion’ package. That would sort of work, but it takes far more CPU than those little boxes have.

The problem is that all that JPEG decoding, motion detection on 640x480 pixels, and JPEG encoding is just too much computation. Fortunately we can be simpler.

Many cheap web cameras have JPEG encoders built in and deliver their images as JPEGs, though sometimes missing a critical table. By keeping a copy of the original camera data it is possible to avoid the JPEG encoding step and just insert the missing data table.

We can do better though. We can avoid most of the decoding and also reduce the data for motion detection by a factor of 64. The key is that luminance of JPEG files is encoded in 8x8 pixel blocks. Each 8x8 pixel block has 64 coefficients which are used to regenerate the 64 pixels of data (more on these coefficients later). The first coefficient is the ‘DC’ coefficient, the average luminosity of the 8x8 block. This is outstanding for motion detection! We get a low pass filter, a factor of 64 data reduction, and all we have to do is not perform a bunch of multiplication and addition in the decoding process.

With a process like the following the tiny router hardware can each support two cameras at 640x480, color, 10 frames per second, motion detection on 5 frames per second.

  1. Read JPEG image from camera over USB.
  2. Hold a copy of the image.
  3. Decode the image enough to extract the DC coefficients of the luminance channel.
  4. Compare to the last frame’s coefficients and decide if there is motion.
  5. If we are going to save the image, then insert the missing JPEG tables into the image if needed and write it out to storage (NFS in my case).
  6. If motion has stopped, then write a sentinel file to independent processes examining the image streams know that the motion event is complete.
  7. Repeat forever.

Astute readers will notice that I can only afford to motion check every other frame with the two camera setup. I’m not happy about this. Essentially all of the CPU time is used in the Huffman decoding of the JPEG data. A long time ago in the age when Vax 8530s roamed my world and I was busy trying to move X-rays and MRIs to radiologists I wrote a state machine based Huffman decoder that could process 3mbits/second of input data, the fastest we could get over Ethernet at the time. Those were 4 MIP machines, these little routers are something like 200MHz. Each camera is generating about 4mbits/second. I have high hopes that this will be doable.

I have other fish to fry and probably won’t get around to the faster huffman code anytime soon. After I run these things for a week or so I’ll release the code in case anyone else wants to read it or use it. I attached the man page in case you wanted to peek at it.

Update: Looks like I have an evil twin in Alexander K. Seewald who has written autofocus software using the AC coefficients. I like his Huffman decoder. I must benchmark it against the slow one I’m currently using to see the range of differences.

Attachments

A word about cheap web cameras:

Many webcams are cheap webcams. Unfortunately some of them cost a lot of money. Logitech I have found to be a crap shoot. Some of their camera models are nice devices, others are utter crap sensors. The problem is you can't tell which is which without buying one. After getting burned with a 'pro' model that was built on a terrible imaging element I now buy web cams that are crap, know they are crap, and are priced like they are crap.

My current favorite is the Aiptek Mini PenCam 1.3, which is a 1.3Mpixel camera, maybe if you count the red, green, and blue elements separately and round up… a couple of times. 640x480 pixels, JPEG encoded, 10 frames per second using the gspca drivers in linux. Their autobrightness logic is insane and will drift off to unintelligible pictures over time, but thats ok, I do my own autobrightness. The gspca driver is wrong about how to set and retrieve the brightness, contrast, and saturation parameters, but I fix that. The nice part is that the cameras are $9.99, with a stand, cable, and a handy leatherette carrying pouch that you can throw in the trashcan.

I don't mind a crappy camera that is honest about it.
Hi - Your motion detection scheme is very interesting! I wonder if you have had a chance to develop it further?
Thanks
Steve
sgulick (at) wildlandsecurity.org 

When writing daemons there is always the question of “How will I check on the status as it runs?“. Solutions include:

  • Have a debug flag and write to stderr.
  • Periodically write a file in a known place.
  • On a particular signal, write a file in a known place.
  • Keep a socket open to write status if someone connects.

I add one more:

  • Have an HTTP interface and let people talk to you with a browser.

libmicrohttpd is just the thing for embedding an HTTP server in your daemon. It adds about 25k to your code, a couple lines to start it and then a dispatching function you write to handle the requests.

It has three models, the least obtrusive of which it is to just let it make some threads and live in its own threads. The others are useful if you can’t tolerate threads, but you have to cooperate to get select() calls made.

I don’t think you’d want to let it take a slashdotting, so either keep it behind a firewall or check the incoming IPs.

It seems to be solid and well written, but I did notice that it raises a SIGPIPE that kills the host application when a connection closes in the middle of a request. (3 word fix in the source. Add MSG_NOSIGNAL to the SEND() calls) That was found in the first minutes of testing, so it doesn’t get the “bullet proof - use and forget” stamp of trust. Maybe after 6 months of daily use it can earn that.

I can't help but wonder though, can it be simpler? I'll probably have to read the HTTP 1.1 spec and see how simple a daemon I can write.
Why are there so many packages ruined by people using autoconfigure and libtool? Just write a simple Makefile and let it be. 

The application where I was going to use libmicrohttpd requires me to crosscompile and after hours of thrashing about I still can't get it to build.

Back to the bit heap with it. I'll write my own httpd code.

Well that wasn't half hard. wc reports 279 lines of code weighing in at 7.5kb source and just under 4k of binary for an HTTP/1.0 and HTTP/1.1 compliant httpd function. (Well, still a few more lines to enforce a maximum concurrent thread limit and a thread timeout so I needn't fear nefarious people… but it is nearly done.)

I thought going in that getting HTTP/1.1 pipelining right was going to be the trickiest part, but on further investigation none of the major browsers use it. Apparently enough web servers screw it up to prevent it.

In the absence of pipelining I decided to forgo keep-alive entirely in favor of simplicity. By careful use of TCP options I only need 3 outgoing packets for each request (up to 1.mumble kbytes). The SYN-ACK, an ACK-Data-FIN, and a FIN-ACK.

An interesting performance issue: Safari shotguns me with many simultaneous connections, to which my httpd responds quickly. If I were supporting keep-alive I think Safari would be encouraged to only use two connections and serialize the requests over them. I wonder which is faster? I may have to add keep-alive support just to answer this question.

Another interesting tidbit: Some people on the web maintain that TCP_QUICKACK and TCP_DEFER_ACCEPT are inherited from the listener socket to the accepted socket. I don't think so. At least the only way I can get QUICKACK turned off is to not use TCP_DEFER_ACCEPT on the listening socket  and slam TCP_QUICKACK off on the accepted socket before the first data arrives. Otherwise I end up sending an ACK before my first data packet.

And a last tidbit: You can keep your TCP_CORK in all the to shutdown(), that gets your FIN piggybacked on your last data packet.

Added the maximum concurrent connection support. It took -3 lines of code. sem_init(), sem_post(), sem_wait() is nicer for this than using pthread mutexes on variables.
Bleh, timeout was harder than I had hoped. I use a simple watchdog thread per request scheme, but even that takes a mutex and some care to get everything deallocated safely.

Worse, if thread A is in an fgets() on file F when it is phtread_canceled, then when thread B tries to fclose(F) it hangs. I suppose there is a lock inside the FILE *. I punted stdio and just did my input at the socket level. I was already doing output at the socket level to avoid a copy operation.

Now to add some comments, forget all about this code, and move on with the actual problem.

openload, which is called openwebload at sourceforge, is a simple URL load tester. Just the right sized tool for checking if your spiffy new web page is efficient enough to be tolerated.

\$ openload localhost 10 URL: http://localhost:80/ Clients: 10 MaTps 355.11, Tps 355.11, Resp Time 0.015, Err 0%, Count 511 MaTps 339.50, Tps 199.00, Resp Time 0.051, Err 0%, Count 711 MaTps 343.72, Tps 381.68, Resp Time 0.032, Err 0%, Count 1111 MaTps 382.04, Tps 727.00, Resp Time 0.020, Err 0%, Count 1838 MaTps 398.54, Tps 547.00, Resp Time 0.018, Err 0%, Count 2385 MaTps 425.78, Tps 670.90, Resp Time 0.014, Err 0%, Count 3072
<You press enter to stop the run> Total TPS: 452.90 Avg. Response time: 0.021 sec. Max Response time: 0.769 sec

There is a Debian package.

Update: I couldn’t leave it alone. The attached patch will add bits/second reports. It is also worth noting that you can add  ”-l number” to sample for a certain number of seconds, and a “-o csv” to put out a CSV line of the data at the end for easier scripting. Perhaps it needs a man page too.

Update: If you are an especially trusting sort of person you could install the Debian package attached and not have to build it yourself.

Update: Best wait on this. There is something odd going on with openload that is giving unstable results for bytes transfered. More diagnosis required.

Attachments

openload.patch 3971 bytes
more articles