apocryph.org Notes to my future self

29Jul/093

Learning to hate cpanel (or, getting subversion HTTP interface working with cpanel)

At least a couple of readers have emailed me to point out my SVN repository has been down for a number of weeks.  It’s not that I haven’t been trying to get it back up, but it’s been a struggle.  As of this morning, it’s finally back online.  If you care what the trouble was, read on.

I recently had to move one of my VPS instances from an Ubuntu box with no fancy pointy-clicky web admin interface to a CentOS box running cPanel.  I’d heard lots of people raving about cPanel, so I figured it’d be a nice to have.  Little did I know.

cPanel works by creating a web interface for nigh on every aspect of running  a web host.  With cPanel, chances are if you do anything the way you would a vanilla Linux box, you’re doing it wrong.  This goes double for anything pertaining to Apache configuration.

I’ve installed SVN and its corresponding Apache module probably at least a dozen times on a blend of Linux and FreeBSD boxes over the years.  Every time it’s been among the easiest, most brainless installs I’ve had to do.  I stupidly thought cPanel wouldn’t change that.  But of course it did.

First off, you can’t just recompile apache from source the way you normally would.  cPanel uses what they jokingly call ‘EasyApache’, where ‘easy’ here means ‘opposite of the Apache you’re used to, and way less flexible’.  To their credit, things that the cPanel guys thought of are pretty easy with EasyApache.  There’s both a web and console GUI for rebuilding Apache that lets you pick what modules to compile in, what version of Apache and PHP to build, and what MPM you want to use.  No problem there, once I figured out I needed to use the script.

But then the trouble started.  I downloaded SVN, and ran configure on it:


sudo ./configure --with-apxs=/usr/local/apache/bin/apxs --with-apr=/usr/local/apache/bin/apr-1-config --with-apr-util=/home/cpeasyapache/src/httpd-2.2.11/srclib/apr-util

Note I had to run this as root because some of the source I needed was in the home directory of another user, cpeasyapache.  Nice.

configure noted that I was missing neon and sqlite, so I downloaded the source tarballs referenced in configure‘s output, and away we went.  It built fine, and sudo make install was uneventful.

At this point I’d already noticed that editing the httpd.conf file directly was Not Cool with cPanel. I noticed this because there’s a massive warning comment block at the top of the file. Instead you use the web interface to edit various include files pulled in by the main conf file. LoadModule calls belong in the ‘Pre Main’ include, which you can edit from the WHM control panel. (Side note: cPanel has two web interfaces on two different ports: 2086 is the WHM interface for configuring the whole box as root; port 2082 is for non-privileged users to configure domains and databases and stuff).

Here’s what I put in the Pre Main include for Apache 2.2.11:


<IfModule mod_dav.c>
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so
</IfModule>

Since the SVN make install already put the relevant modules in the modules directory, saving this include file and automatically restarting the Apache service worked in as much as apachectl -d DUMP_MODULES listed the two SVN modules as loaded.

Next, I had to figure out where to put the Location entry for my svn.apocryph.org virtual host. I found this article which explained how global and vhost-specific custom directives can be added. In my case my SVN domain was an add-on domain for my existing user account, so I created a svn.conf file at /usr/local/apache/conf/userdata/std/2/anelson/svn.anelson.bulshytt.com that went a little something like this:

# Configure subversion to handle the /svn stuff
<Location /svn>
DAV svn
SVNPath /var/svn/repos

# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /etc/svn-auth-file

# For any operations other than these, require an authenticated user.
<LimitExcept GET PROPFIND OPTIONS REPORT>
Require valid-user
</LimitExcept>
</Location>

Obviously I used htpasswd to create the /etc/svn-auth-file file.

This all seemed straightforward, then I ran /scripts/verify_vhost_includes as per the above article, and I got this:


Testing /usr/local/apache/conf/userdata/std/2/anelson/svn.anelson.bulshytt.com/svn.conf...FAILED
No changes made without --commit flag

I then tried it with the --show-test-output switch:


Testing /usr/local/apache/conf/userdata/std/2/anelson/svn.anelson.bulshytt.com/svn.conf...FAILED
No changes made without --commit flag
[TEST RESULTS]
Syntax error on line 3 of /usr/local/apache/conf/userdata/std/2/anelson/svn.anelson.bulshytt.com/svn.conf:
Unknown DAV provider: svn

[/TEST RESULTS]

‘Unknown DAV provider’? But I verified the SVN modules are loaded! Tons of googling mostly yields assclowns who forgot the LoadModule directive, but that’s not me. Finally I ran across this guy bitching in the cpanel forums.  He’s having the exact same problem.  The responses from the cPanel guys are telling.  Basically since the SVN module isn’t one they thought of, that makes this a ‘custom’ configuration that would require an EasyApache Custom Module to make work right.  Fuck that.  What happened to ‘Easy’?

The good news is this excerpt:

If we only use /scripts/ensure_vhost_includes –user=domain and restart apache, subversion works perfectly, but wonder why the “DAV svn” is not recognize as valid?

Interesting. So the change will fail to verify, but if you force the include anyway, everything works? Let’s try it:


/scripts/ensure_vhost_includes --user=username
/scripts/rebuildhttpdconf
/scripts/restartsrv_httpd

Voila! svn.apocryph.org/svn now shows the SVN repository. But there are downsides:

  • The EasyApache builder doesn’t know about the SVN modules, so whenever I rebuild Apache the two modules will be clobbered, which will cause my Pre Main include modifications to fail to load the SVN modules.  So to rebuild I’ll have to comment out those LoadModule lines, rebuild, repeat the ‘make install’ command for SVN, then restore the LoadModule lines.
  • the verify_vhost_includes script will always complain about the change I made for my svn.apocryph.org vhost.  If I forget I did this, I might make some other vhost change down the road and scratch my head wondering why this SVN stuff that’s worked forever isn’t working now
  • It took fucking hours to get going, with absolutely zero official documentation from cPanel

At this point, if I had the ability to move my VPS instance again, to a host without cPanel, I absolutely would.  It gets in my way at every step of the way, and the pointy-clicky GUI bullshit is nearly worthless to me as it either automates things I already can do comfortably with the command line, or makes simplifying assumptions that don’t meet my requirements.

UPDATE: After I posted this I tried to commit to the repository, only to discover I could not.  Any attempts to commit first prompted me for my credentials, as expected, but when creds were provided I got this failure:


Commit failed (details follow):
Server sent unexpected return value (403 Forbidden)
in response to PUT request for
'/svn/!svn/wrk/d3def008-ccd0-11dd-88ba-e715122b690d/test.txt'

This despite having correct file system permissions. I Googled around and found another guy, also on a VPS as it happens, who solved this by adding


Order allow,deny
Allow from all

I tried adding that to the Location block in my svn.conf file, and sure enough that did the trick. But why? My sense is there’s some module or security setting that’s blocking PUT requests, but I can’t imagine what it would be. Remind me again why this managed VPS thing is better than building my own VPS from scratch?

5Dec/086

I've said it before, and I'll say it again: Side-by-side assemblies are WAY worse than DLL Hell

I’ve bitched at length about Microsoft’s side-by-side scheme for ending the “DLL hell” that used to rule Windows, whereby multiple applications would each write their preferred DLL to Windows\System32, and break other apps that expected a different version. Microsoft set out to fix this problem with what I can only assume was a room full of PhD’s and no input from working programmers or sysadmins, since the resulting abomination takes a hard, non-obvious problem and replaces it with a very hard, virtually inscrutable problem.

Twice so far in the last 24 hours has this has bit me in the ass. First, I was trying to run some code on a test VM which I built on my development machine. Since I wanted to, you know, debug this code, I created a ‘debug’ build, which links the executable with the debug version of the Microsoft C runtimes. Every developer who’s ever tried to do this knows it won’t Just Work, since the debug runtimes won’t be present on the box. What I used to do was manually copy all the DebugCRT and DebugMFC files from my dev machine’s c:\windows\winsxs directory into the destination machines, which is exactly what Microsoft admonishes us not to do, but they don’t leave me much choice.

However, this VM I’m using this time is Windows Server 2008, whereon even the mighty Administrator doesn’t have write privs to winsxs; that’s limited to the Trusted Installer user. Sure, I could munge the privs, but this is supposed to be a test box, so I don’t want to munge it too far afield of what our customers’ configs will look like. So, I do what the WinSxs (say “win sucks”, because it does) guys suggest is to find the Debug_NoRedist folder in the Visual Studio 2008 install directory, copy the folders there under your architecture directly into the directory where your application is located, and voila!, through the magic of WinSxs, it will Just Work.

And, it would have. Except the Visual Studio team apparently doesn’t really get WinSxs either, because after I installed Visual Studio 2008 SP1, it updated the Debug_NoRedist folder with the new SP1 runtimes, which includes manifests that reflect the SP1 version of the debug CRT (9.0.30729.1), and not the old RTM version (9.0.21022.8). That’s the right call, except the binaries produced by SP1 have in their embedded assembly manifest a reference to the CRT version 9.0.21022.8. This works fine on a dev workstation where both the RTM and SP1 versions of the runtimes are installed, since there’s a policy file in the WinSxs directory that redirects requests for 9.0.21022.8 to the 9.0.30729.1 version. However, when you’re doing an isolated application that loads the CRTs from the current directory, there is no such redirect, so the app looks for 9.0.21022.8 and can only find 9.0.30729.1, so it eats shit and dies.

The fix? You’ll love it. Edit the Microsoft.VC90.DebugCRT.manifest file to change the version number from 9.0.30729.1 to 9.0.21022.8, and QED. Yes, that makes me feel dirty, but at least it works, which is more than I can say for the so-called “correct” way.

To whichever team came up with WinSxs: FUCK YOU ASSHOLES!

The second and more subtle way in which this bit me in the ass had to do with using CreateProcess to spawn an executable with a path like this: c:\foo\bar..\boo\baz.exe; that is, a .. somewhere in the path. That works fine normally, but if we’re using the debug runtimes, and there’s a Microsoft.VC90.DebugCRT directory in c:\foo\bar and also one in c:\foo\boo, then I get crashes the first time I allocate memory from a DLL and free it from the process. I can verify only one instance of the CRT DLLs are loaded into the process, and I can verify the DLL is compiled correctly, because when I switch from c:\foo\bar..\boo\baz.exe to c:\foo\boo\baz.exe with no other changes, it works fine.

Is this WinSxs’ fault or the CRT’s fault? Does it really matter? No: FUCK YOU ASSHOLES!

23Oct/082

Creating DevExpress.NET toolbox icons for non-admin users

At work we use the DevExpress .NET widget toolkit to build flashy GUIs. I recently repaved my dev box, and now run as a non-admin user most of the time. This often causes problems with badly-behaved software, which apparently includes the DevExpress .NET toolkit.

If I run the installer as an admin, it creates convenient toolbox items in Visual Studio 2008 for dropping various DevExpress controls into the forms I build. However, my non-admin user doesn’t get these toolbox items. There’s a separate tool, the Toolbox Creator, which ships with DevExpress for the purpose of putting these back, but it doesn’t work without admin privs (see here).

DevExpress’s handy advise is:

The ToolBoxCreator creates toolbox icons only for the user for which it is launched. And, this user must have Administrator rights. This behavior is by design.
Besides, running the VS 2008 under a user without administrator rights leads to many issues

Well, fuck you. Maybe you can’t write code that behaves properly without admin privs, but that doesn’t mean I can’t.

I figured out a hack to get their shit working. Use Aaron Margosis’ MakeMeAdmin batch file to create a command window which is running as you, but with admin privs. cd into the DevExpress Tools directory, then run the target of the ToolboxCreator shortcut. Voila!

For the most part I’m happy with the DevExpress toolkit, but this incident really pissed me off. There’s no earthly reason why the Toolbox Creator needs admin rights, since the toolbox settings are per-user. It’s just lazy programming.

15Aug/080

Annoyances in .NET XML libraries

At work I’m building a simple tool to populate a FogBugz wiki page with build information. One of the things this tool needs to do is pull the XHTML contents of a wiki page, parse it (as XML), and take action on the resulting document tree. Initially I expected this to be stupid-easy, as XHTML is just XML, right?

Au contrare!

Problem 1: XHTML is NOT just XML

The first problem is XHTML documents likely contain entity references like &nbsp; and whatnot. These entity references aren’t XML entities, they’re XHTML entities, so you must load the XHTML DTD in order to resolve them. Trouble is, this means there must be a proper XHTML DOCTYPE directive in your XHTML (which there isn’t in my case since I’m using fragments).

Once a valid DOCTYPE directive is added to the XHTML, now .NET will download the full DTD from W3 just to parse a little XHTML fragment. Not acceptable. So, I had to download the XHTML strict DTD, and the three dependent DTDs containing entity definitions, and paste them together to create one big DTD with all the XHTML entities inline. I then added that file to my project as an embedded resource, and ripped off this code to write an HtmlResolver subclass of XmlUriResolver to intercept requests for the XHTML DTDs, and instead pull my jumbo DTD out of a resource stream and return that.

Problem 2: Automatic XHTML namespace

The second problem cropped up when I tried to issue an XPath query for all the h1 elements in my markup. For some reason, the call to SelectNodes was always returning zero matches, even though I know the XHTML contained a multitude of h1 elements. The cause became clear when I looked at the InnerXml property of my XmlDocument object. Something was adding a xmlns:namespace attribute to the html element I was generating for the root of my document tree, even though the string from which the XmlDocument was generated never explicitly specified a namespace. Since I was issuing an XPath query for h1 elements in the global namespace, and the parser put all the elements in the html namespace, it was silently skipping all my h1 elements!

The fix was to replace html as my root node with something not in the XHTML DTD, like wikipage. Still, that should not happen!

Idea for .NET 4.0: XHTML support, built in!

22May/082

PHP Sucks

My work on a tool to migrate Drupal content to WordPress’ eXtended RSS (‘WXR) led me into some dusty corners of the WordPress codebase, and I’ve been meaning to write a grumpy post about how much I hate PHP (in which WordPress is written), but Jeff Atwood at Coding Horror beat me to it with his own PHP sucks lament. Like me, Jeff wonders at the success of PHP given what a dreadfully sucky software engineering tool it is, and scratches his head at the many major Internet properties (Wikipedia, Digg, and WordPress among them) which are successful notwithstanding an implementation in a language a VB6 programmer might reasonable call “shit”.

Interestingly, though, Jeff and I arrived at two different conclusions on the matter. Jeff surmises:

Some of the largest sites on the internet — sites you probably interact with on a daily basis — are written in PHP. If PHP sucks so profoundly, why is it powering so much of the internet?

The only conclusion I can draw is that building a compelling application is far more important than choice of language. While PHP wouldn’t be my choice, and if pressed, I might argue that it should never be the choice for any rational human being sitting in front of a computer, I can’t argue with the results.

You’ve probably heard that sufficiently incompetent coders can write FORTRAN in any language. It’s true. But the converse is also true: sufficiently talented coders can write great applications in terrible languages, too. It’s a painful lesson, but an important one.

Hmm. I came to a different conclusion. Though I don’t know any of the programmers at the major PHP-powered properties, I have looked at some of their code, and I must say it does not look like the product of talented software engineers making due with a shit language; it looks like cruft bodgered together until it works, as though the last twenty years of hard-won software engineering advancements never happened.

To take but one example, I am working on some code that exports a Drupal site in WordPress’ WXR format, which is basically RSS with some additional WordPress-specific elements thrown in. Of course the format isn’t documented at all, so I set up a simple WordPress site, wrote some test posts with attachments and comments and whatnot, exported the site to WXR, and used the resulting WXR file as an example upon which to base my own implementation. I’ve mostly got it to work now, but I ran into some troubles along the way due entirely to the poor construction of at least the WXR import portion of the WordPress codebase.

To start with, WXR, like RSS before it, is an XML format. XML, having been around for over a decade now, is a very well-understood format. One of the fundamental principles of XML is that whitespace doesn’t matter. You can put text right after a start tag, or on the next line indented with a tab, or after several blank lines, and it will parse just the same (some exceptions but bear with me). I foolishly assumed that since RSS uses XML, and WXR extends RSS, that I needed only produce XML that was structurally identical to the WXR sample I had, without regard for whitespace. When I imported the first WXR file from my tool, WordPress didn’t complain but I noticed none of my content was coming through. WTF?

I then cracked open the import code at wp-admin/import/wordpress.php. Once there I stared in horror as I realized what they were doing. Rather than parsing the WXR file with, you know, an XML parser, the author of this particularly craptastic bit of code used a regular expression to find certain tag pairs, hard-coding the namespace prefix and assuming no whitespace between the start tag, content, and end tag. Super!

Not content merely with a gobsmackingly vile solution to a problem solved 10 years ago by the XML DOM, the code does neato things like injecting local variables into the local namespace non-obviously with calls to helper functions, and performing what amounts to zero error handling. For example, there was a bug in my stuff that migrated blog posts with a type attribute of blog, when it should have been post. Strangely, the import into WordPress ran without error, but none of the migrated blog posts showed up. Curious, I looked inside the WordPress MySQL database, and found all of my missing posts there; they just weren’t showing up. Rather than sanity-check the WXR input, the import logic just sucked it dutifully in, so future queries for type='post' would happily skip over my content without so much as a peep.

While I do have some sympathy for those unfortunate bastards who must work with a shit tool like PHP, I know for a fact that it’s not impossible to engineer quality code with it. If you doubt me, download the source tarball for Gallery 2 and find out how the MVC pattern, unit testing, and object orientation are indeed possible in PHP.

So what to make of this? I don’t think software engineering quality has anything to do with the success of a piece of software. Pristine, gleaming, perfectly coherent code doesn’t seem at all correlated with success, and clumsy bodgered-together cruft seems the rule and not the exception at the top of the software pile. Can you find well-engineered software that was successful? Sure. Can you point out spectacularly monstrous code that failed? Even easier. But explain to me how crufty software like Mediawiki and WordPress can be such runaway successes while Gallery and Drupal remain relatively obscure, if software engineering quality is so critical to success?

Now don’t get me wrong. I’m not one of those backward types who doesn’t see the point in unit testing, thinks object orientation is too complicated, and wouldn’t know a bad code smell from roadkill. I take the practice of software engineering seriously, and strive to build the best software I can within the constraints of our business. In this regard I’m more like the second stonecutter in Peter Drucker’s Three Stonecutters parable:

A favorite story at management meetings is that of the three stonecutters who were asked what they were doing. The first replied, ‘I am making a living.’ The second kept on hammering while he said, ‘I am doing the best job of stonecutting in the entire country.’ The third one looked up with a visionary gleam in his eyes and said, ‘I am building a cathedral.’

The third man is, of course, the true ‘manager.’ The first man knows what he wants to get out of the work and manages to do so. He is likely to give a “fair day’s work for a fair day’s pay.”

It is the second man who is a problem. Workmanship is essential; without it no business can flourish; in fact, an organization becomes demoralized if it does not demand of its members the most scrupulous workmanship they are capable of. But there is always a danger that the true workman, the true professional, will believe that he is accomplishing something when in effect he is just polishing stones or collecting footnotes. Workmanship must be encouraged in the business enterprise. But it must always be related to the needs of the whole.

… The tendency to make the craft or function an end in itself [in future] will therefore be even more marked than it is today. … The new technology will need both the drive for excellence in workmanship and the consistent direction of managers at all levels toward the common goal.”

I don’t accept that disciplined software engineering is equivalent to “stone polishing”, but I think the point is that past a certain qualitative point (which is different for each activity and each situation) improvements in workmanship don’t contribute meaningfully to value in the marketplace, and if undertaken at the expense of other activities which could have increased value can actually result in a net loss of productivity.

From the success of WordPress and Mediawiki and Youtube, I think it’s hard to make the case that more time and resources should’ve been spent architecting and writing better PHP code, but I don’t know how one can determine a priori where the productive labors stop and the stone polishing starts. If PHP has one thing going for it, it’s that it makes anything but the most cursory stone polishing so clumsy and uncomfortable that only the most pedantic of programmers would bother. Make of that what you will.

6Apr/082

Kneecapped by major DreamHost outage

The hosting provider for apocryph.org, DreamHost, is experiencing a serious outage that has resulted in apocryph.org sucking serious wind and/or being completely offline. DH’s latest claim is a fix from their storage vendor will cure all ills, but this has been going on for a week now so it’s hard to imagine a QFE from a vendor will just make it vanish.

Anyway, I probably brought this down upon myself, since I went from fairly-cheap-but-still-shitty CI Host to considerably-cheaper DreamHost, based on the promise of ridiculous expanses of disk space and lavish transfer quotas. Of course, I probably should have asked myself how they could afford such generosity, but in the face of mind-boggling cheapness it’s hard to be objective.

Even though apocryph’s traffic numbers are asymptotic to zero it still really pisses me off to experience such a nasty outage. I wanted to work on my Drupal-to-Wordpress migration tool today, but the server hosting Drupal is so blasted even the shell is unusably slow. I’d threaten to pack up my shit and go to another host, but I don’t know where I’d go. I didn’t just pick DreamHost at random; they really due have insane disk and transfer quotas (I think I’m at 500GB and multiple terabytes of transfer, for something less than $20/mo), and as displeased as I’ve been with their reliability lately they have a much lower fail factor than the made-in-China discount hosts in the same price range.

Dammit, why can’t hosting be fast, good, AND cheap?

10Aug/070

Way to go, Frank

No sooner had I finished excorciating Rep. Frank Wolf for his spendy ways than did Reason find a worse example of Wolf’s feckless insanity. Reading the original NYT article, it’s a classic Wolfian solution to a non-problem: federal moneys go to an anti-porn crusader organization which hires retired cops to investigate porn reported by concerned citizens for possible obscenity violations. Total prosecutions based on discovered ‘obscenities’: zip.

This from a Congressman whose district includes the town of Herndon, home to the highest proportion of foreign-born residents in the area, and hotbed of illegal immigration proxy wars. Rather than deal with actual problems, apparently Wolf finds it easier to give anti-pr0n busybodies some of my money. If anyone has a decaying corpse they’d like to run in the next race for Wolf’s seat, please let me know, as I’ll happily support it.

8Jun/070

Shitty Visual C++ Gotcha

Whilst building Wireshark I ran into a nasty gotcha in Visual C++. If you set a preprocessor definition in the Preprocessor screen and you use quotes, like _U_="", the IDE happily accepts it, but shit breaks wierdly. Errors like

 cl : Command line error D8003 : missing source filename

and

 LINK : fatal error LNK1561: entry point must be defined

Came out of the woodwork. It turns out the quotes in the preprocessor definition, once applied to an actual command line, thoroughly fuck things up. How is it that we’re still dealing with shit like this in 2007?

17May/072

Bullshit Visual C++ 2005 regression bug

I’m working on building a Win32 static library version of zlib, which uses some .asm files. One of these files, inffas32.asm, contains this line (line 647):

 movd mm4,[esp+0]

This line fails to assemble with the following error:

error A2070: invalid instruction operands

I only found one mention of this problem with zlib, an incoherent blog post here. It links to a Microsoft bug report which has this to say:

Microsoft Macro Assembler 8.0, included with Visual C++ 2005 Express Beta 1, refuses to assemble some instructions that were valid under MASM 7.1 included with Visual Studio .NET 2003. Specifically, it refuses to assemble a MOVD instruction with a memory operand with an implied size, and requires that “dword ptr” prefix the memory operand. Although this is ambiguous under AMD64, it is not ambiguous in IA32 and should not be an error, particularly as this behavior is a regression from 7.1 and breaks existing valid source code.

The resolution?

this isnt a bug, rather its syntax that was previously allowed that should not have been. The “fix” here is to use the correct operand sizes in the syntax.

Goddamit, that’s helpful. Sure, the code is technically wrong. But it’s been technically wrong since at least Visual Studio 6.0 some eight years ago, and has worked all this time. You’d think there’d be at least a ‘heads up, we’re about to fuck you’ note somewhere. So now I guess I have to go through and change all the movd instructions to use explicit pointer sizing. Fuck.

UPDATE

It seems there are already Visual Studio 2k3 and 2k5 project files in the contrib\vc7 and contrib\vc8 folders respectively in the zlib source tarball. They seem to work quite a bit better. Still, that doesn’t get MS off the hook for a real dick move.

7Apr/070

Highlander: The Source ruined the Highlander franchise for good

A while back, my pirate friend brought over a rip of Highlander: The Source, the latest movie in the Highlander franchise. I enjoyed the series as a teen, and the last movie, Highlander: End Game, was pretty good. Sure, this one got bad reviews on IMDB, including the dubious claim that it was the “worst movie ever”, but I figured the reviewer just didn’t get the Highlander Zen.

Sadly, my faith was misplaced. “The Source” really is_ the worst movie ever made. So bad, in fact, that it managed to erase all my past enjoyment of the Highlander franchise and replace it with bitterness and disgust. It’s _that bad.

The plot? There wasn’t one. The dialog? Excruciating. The acting? Horrifying. The SFX? Well, does fast-forwarding action in a sort of reverse-Matrix effect count?

I want those two hours of my life back, dammit!

Delicious Bookmarks

Recent Posts

Meta

Current Location