Russ Garrett http://russ.garrett.co.uk Mon, 15 Apr 2013 18:07:40 +0000 en hourly 1 Driving Monitoring Displays in Linux http://russ.garrett.co.uk/2013/04/15/driving-monitoring-displays-in-linux Mon, 15 Apr 2013 00:00:00 +0000 http://russ.garrett.co.uk/2013/04/15/driving-monitoring-displays-in-linux <p>After a rather long hiatus, here&#8217;s a bit of a technical brain dump while I have all the details in my mind.</p> <p>I like graphs, and I like having them displayed on dedicated screens. My preferred way of doing this is to use Chrome on Linux to display the monitoring web page (generated by the likes of <a href='https://github.com/lozzd/Naglite2'>naglite2</a>/<a href='https://github.com/lozzd/nagdash'>nagdash</a> or <a href='https://github.com/lozzd/cactiview'>cactiview</a>/<a href='https://github.com/russss/graphview'>graphview</a>):</p> <p>Whenever I set one of these machines up, I always spend an inordinate amount of time looking up obscure incantations on the internet. So here&#8217;s the comprehensive guide to setting one of these machines up. (Where these things are distribution-specific, they&#8217;re Debian/Ubuntuisms.)</p> <h3 id='starting_x_up'>Starting X up</h3> <p>You don&#8217;t want to fiddle with these machines using VNC/RDP, so forget about window managers. Remove the desktop manager (<code>apt-get remove [mdm|gdm|xdm|...]</code>), and give your non-root user the rights to start an X server in <code>/etc/X11/Xwrapper.config</code>.</p> <p>Now we can get X11 to run Chrome in the root window directly, by editing <code>~/.Xinitrc</code>:</p> <div class='highlight'><pre><code class='bash'><span class='c'>#!/bin/sh</span> <span class='nb'>exec </span>google-chrome --no-default-browser-check --kiosk http://server/monitoring </code></pre></div> <p>We&#8217;re using <code>--no-default-browser-check</code> to suppress the initial welcome dialog, and <code>--kiosk</code> full-screens Chrome by default. Nonetheless, depending on your Chrome version, you might still need to feed it a pre-existing profile (in <code>~/.config/google-chrome</code>) to stop it from popping things up which you need to click on.</p> <p>To start X11, I use a short looping wrapper script, passing the <code>-nocursor</code> X option to stop the annoying cursor:</p> <div class='highlight'><pre><code class='bash'><span class='c'>#!/bin/bash</span> <span class='k'>while</span> <span class='o'>[</span> 1 <span class='o'>]</span>; <span class='k'>do</span> <span class='k'> </span>xinit /home/screens/.Xinitrc -- -nocursor sleep 10 <span class='k'>done</span> </code></pre></div> <p>I run this backgrounded from a crontab <code>@reboot</code> task. If you need to reload Chrome for any reason, <code>pkill chrome</code> will force an X restart (and without a window manager that&#8217;s a pleasingly swift operation).</p> <h3 id='dealing_with_screen_blanking'>Dealing With Screen Blanking</h3> <p>You&#8217;ll probably find that the screen blanks after 15 minutes, which is pretty annoying. Disabling this completely when you have no window manager is fiddly, but the best way is to alter your <code>xorg.conf</code> like so:</p> <pre><code> Section &quot;ServerFlags&quot; Option &quot;blank time&quot; &quot;0&quot; Option &quot;standby time&quot; &quot;0&quot; Option &quot;suspend time&quot; &quot;0&quot; Option &quot;off time&quot; &quot;0&quot; EndSection</code></pre> <p>That inhibits screen power saving completely, but we like the environment so we should probably switch it off when everyone&#8217;s gone home. You can do this using the <code>xset</code> command, through cron or similar:</p> <pre><code> DISPLAY=:0 xset dpms force [off|on]</code></pre> <h3 id='multiple_displays'>Multiple Displays</h3> <p>Perhaps you want to attach multiple displays to one machine. As you&#8217;d expect, X makes this appropriately difficult. The default state for Xorg these days is to create a single desktop spanning both displays, but when you want to maximize a window on each screen, and without the assistance of a window manager which supports some kind of scripting, this isn&#8217;t ideal.</p> <p>What we want is a separate X screen on each physical display, which is (of course) called &#8220;Zaphod Mode&#8221;. In the <code>xorg.conf</code>:</p> <pre><code> Section &quot;Device&quot; Option &quot;ZaphodHeads&quot; &quot;DVI-0&quot; Identifier &quot;Card0&quot; Driver &quot;radeon&quot; BusID &quot;PCI:1:0:0&quot; Screen 0 EndSection Section &quot;Device&quot; Option &quot;ZaphodHeads&quot; &quot;VGA-0&quot; Identifier &quot;Card1&quot; Driver &quot;radeon&quot; BusID &quot;PCI:1:0:0&quot; Screen 1 EndSection Section &quot;Screen&quot; Identifier &quot;Screen0&quot; Device &quot;Card0&quot; EndSection Section &quot;Screen&quot; Identifier &quot;Screen1&quot; Device &quot;Card1&quot; EndSection Section &quot;ServerLayout&quot; Identifier &quot;default&quot; Screen &quot;Screen0&quot; 0 0 Screen &quot;Screen1&quot; LeftOf &quot;Screen0&quot; EndSection</code></pre> <p>Once this ugly deed is done, instead of one X display called <code>:0</code> you&#8217;ll have two, called <code>:0.0</code> and <code>:0.1</code>. To take advantage of both, you&#8217;ll need to use two separate Chrome profiles (or it&#8217;ll try and be clever, and open a new window on the same display as the existing session). Copy <code>~/.config/google-chrome</code> to <code>~/.config/google-chrome-2</code> and alter your .Xinitrc as follows:</p> <div class='highlight'><pre><code class='bash'><span class='c'>#!/bin/sh</span> <span class='nv'>DISPLAY</span><span class='o'>=</span>:0.0 <span class='nb'>exec </span>google-chrome --no-default-browser-check --kiosk http://server/screen1 &amp; <span class='nv'>DISPLAY</span><span class='o'>=</span>:0.1 <span class='nb'>exec </span>google-chrome --no-default-browser-check --kiosk <span class='se'>\</span> --user-data-dir<span class='o'>=</span>/home/screens/.config/google-chrome-2 http://server/screen2 </code></pre></div> <h3 id='conclusion'>Conclusion</h3> <p>This should be a chef cookbook. I&#8217;m planning on putting one together for our next monitoring project (which involves the six Raspberry Pis currently on my desk&#8230;).</p> Health and Safety http://russ.garrett.co.uk/2012/01/21/elf-and-safety Sat, 21 Jan 2012 00:00:00 +0000 http://russ.garrett.co.uk/2012/01/21/elf-and-safety <p>Every time I get depressed about things being shut down because of &#8220;Health and Safety&#8221; and the compensation culture, I like to take time to read the judgment in <a href='http://www.judiciary.gov.uk/Resources/JCO/Documents/Judgments/grimes-v-hawkins-frimley-park-hospital.pdf'>Grimes vs Hawkins et al (2011)</a> (pdf).</p> <p>It&#8217;s a court case filed by an extremely unfortunate 18-year-old (Grimes) who ended up at a friend&#8217;s house late at night, misjudged a dive into the pool, shattered her spine, and ended up tetraplegic. Subsequently she sued the friend&#8217;s dad (Hawkins) and, rightfully, lost.</p> <p>I&#8217;d encourage you to read it in full, because it&#8217;s probably one of the most reasonable, clearly-stated, sensible pieces of justice ever administered.</p> <blockquote> <p>The pool was not unsafe for diving. I have no doubt that some mature adults faced with a group of young adults in high spirits, some of whom had had too much to drink, would send them all home rather than allow any of them into a swimming pool. But that is not to say that the duty owed to the claimant under the <a href='http://www.legislation.gov.uk/ukpga/Eliz2/5-6/31/contents'>Occupier’s Liability Act 1957</a> required the defendant to put the pool out of bounds that night. The defendant was not required to adopt a paternalistic approach to his visitors, all of whom were adults, all of whom were making choices about their behaviour, exercising their free will.</p> </blockquote> Green Threads and Pipes in Python http://russ.garrett.co.uk/2011/12/16/green-threads-and-pipes-in-python Fri, 16 Dec 2011 00:00:00 +0000 http://russ.garrett.co.uk/2011/12/16/green-threads-and-pipes-in-python <p>I&#8217;ve been hacking on <a href='https://github.com/heroku/WAL-E'>WAL-E</a>, a nice little Postgres backup system from Heroku which uses gevent for concurrency. Much of my changes are related to UNIX pipelines, and I&#8217;ve run into a subtle issue which not only affects gevent but also Eventlet (which is our coroutine library of choice at Smarkets).</p> <p>Here&#8217;s a trivial example &#8212; an Eventletized version of <a href='http://docs.python.org/library/subprocess.html#replacing-shell-pipeline'>an example</a> in the Python manual:</p> <div class='highlight'><pre><code class='python'><span class='kn'>from</span> <span class='nn'>eventlet.green.subprocess</span> <span class='kn'>import</span> <span class='n'>Popen</span><span class='p'>,</span> <span class='n'>PIPE</span> <span class='n'>fp</span> <span class='o'>=</span> <span class='nb'>file</span><span class='p'>(</span><span class='s'>&#39;./input.file&#39;</span><span class='p'>,</span> <span class='s'>&#39;r&#39;</span><span class='p'>)</span> <span class='c'># Should be reasonably large</span> <span class='n'>tf</span> <span class='o'>=</span> <span class='nb'>file</span><span class='p'>(</span><span class='s'>&#39;./output.file&#39;</span><span class='p'>,</span> <span class='s'>&#39;w&#39;</span><span class='p'>)</span> <span class='n'>p1</span> <span class='o'>=</span> <span class='n'>Popen</span><span class='p'>([</span><span class='s'>&#39;sort&#39;</span><span class='p'>],</span> <span class='n'>stdin</span><span class='o'>=</span><span class='n'>fp</span><span class='p'>,</span> <span class='n'>stdout</span><span class='o'>=</span><span class='n'>PIPE</span><span class='p'>)</span> <span class='n'>p2</span> <span class='o'>=</span> <span class='n'>Popen</span><span class='p'>([</span><span class='s'>&#39;cat&#39;</span><span class='p'>,</span> <span class='s'>&#39;-&#39;</span><span class='p'>],</span> <span class='n'>stdin</span><span class='o'>=</span><span class='n'>p1</span><span class='o'>.</span><span class='n'>stdout</span><span class='p'>,</span> <span class='n'>stdout</span><span class='o'>=</span><span class='n'>tf</span><span class='p'>)</span> <span class='n'>p1</span><span class='o'>.</span><span class='n'>stdout</span><span class='o'>.</span><span class='n'>close</span><span class='p'>()</span> <span class='n'>p1</span><span class='o'>.</span><span class='n'>wait</span><span class='p'>()</span> <span class='n'>p2</span><span class='o'>.</span><span class='n'>wait</span><span class='p'>()</span> </code></pre></div> <p>You&#8217;ll get the following error:</p> <pre><code>cat: -: Resource temporarily unavailable sort: write failed: standard output: Broken pipe sort: write error</code></pre> <p>The problem is that you&#8217;re not expected to actually pipe data between separate processes. Eventlet assumes that you&#8217;ll be using the <code>p1.stdout</code> file descriptor from within your Python process, and it helpfully <a href='https://bitbucket.org/which_linden/eventlet/src/a60be8a9cdb5/eventlet/greenio.py#cl-128'>marks it as non-blocking</a> for you so methods like <a href='http://docs.python.org/library/subprocess.html#subprocess.Popen.communicate'>communicate</a> won&#8217;t block. When you hand that file descriptor to <code>cat</code>, the flags are preserved, and <code>cat</code> isn&#8217;t happy when it tries to read from what it thinks is a blocking socket and gets <code>-EAGAIN</code>.</p> <p>Gevent doesn&#8217;t have a patched version of the <code>subprocess</code> library, but the pattern of patching stdin and stdout of Popen is repeated in a lot of gevent-using code, including <a href='https://github.com/heroku/WAL-E/blob/master/wal_e/piper.py#L22'>within WAL-E itself</a>.</p> <p>I&#8217;m not sure if there&#8217;s an easy fix for this; the code can&#8217;t know whether you&#8217;ll be using the pipe yourself or passing it onto another process. In any case I&#8217;d rather be explicit about changing the options.</p> Facebook's Scrobbler http://russ.garrett.co.uk/2011/09/22/new-facebook-features Thu, 22 Sep 2011 00:00:00 +0000 http://russ.garrett.co.uk/2011/09/22/new-facebook-features <p>We knew Facebook was going to announce a big music-related feature today, and the rumour was that it was going to basically be scrobbling. So a small army of Last.fmers and ex-Last.fmers convened our legendary keynote backchannel on IRC to watch it.</p> <p>It almost crept up on me. This was a developer event, so Zuckerberg (as inexpert as he is at giving these talks) assembled their scrobbling feature in front of us. It&#8217;s a combination of their &#8220;frictionless&#8221; way of enabling apps to post updates, and the new &#8220;verb&#8221; system which adds a bit of semantic structure to them.</p> <p>At Last.fm we always said that we&#8217;d love to see other types of action scrobble, but finding actions which fit the scrobbling concept was hard. The key part of scrobbling is the automation, it can&#8217;t be a manual thing.</p> <p>Facebook have solved this - they&#8217;re now a generic platform for scrobbling anything. Add that to the fact that there are an increasingly large number of things which <em>can</em> automatically scrobble &#8212; books from my Kindle, films through Netflix &#8212; and I think Facebook may have solved it.</p> <p>Of course Facebook can&#8217;t scrobble from desktop media players yet, but that feature will come. Last.fm could provide it, but then it would increasinlgly be just a commodity.</p> <p>The music features Facebook has built are fairly lacklustre. But I don&#8217;t think that matters - they have practically a billion-user head start on Last.fm, and the convenience will soon outweigh the experience Last.fm has gained for talking to music-lovers.</p> <p>It&#8217;s going to be an interesting few months.</p> Visiting the Red Sands Sea Fort http://russ.garrett.co.uk/2011/08/21/red-sands-sea-fort Sun, 21 Aug 2011 00:00:00 +0000 http://russ.garrett.co.uk/2011/08/21/red-sands-sea-fort <p>Yesterday morning, close to a year&#8217;s worth of planning and aborted attempts finally came to fruition. Twelve of us involved with London Hackspace took a train from Victoria to Whitstable, then boarded a boat for the 45-minute journey to the Red Sands sea fort.</p> <a href='http://www.flickr.com/photos/russss/sets/72157627355971457'> <img src='http://farm7.static.flickr.com/6186/6064061270_e9c1e75a93_z.jpg' /> </a> <p>With the weather not looking too promising, we managed to hop onto the fort before the wind picked up and it started raining. Half an hour later, though, the August weather finally cut us a break.</p> <a href='http://www.flickr.com/photos/russss/sets/72157627355971457'> <img src='http://farm7.static.flickr.com/6200/6063535489_842544554c_z.jpg' /> </a> <p>Red Sands is one of two remaining <a href='http://en.wikipedia.org/wiki/Maunsell_Forts'>Maunsell sea forts</a> built by the army in the Thames estuary, and during World War II it housed six anti-aircraft guns. (The other is Shivering Sands, but that&#8217;s likely to be demolished at some point because it poses more of a hazard to shipping.) After being abandoned in the 1950s they&#8217;ve been standing empty, give or take a few pirate radio stations, slowly rusting and falling into the North Sea.</p> <p><a href='http://www.project-redsand.com/'>Project Redsand</a> is trying to change that by gradually restoring the forts to their former glory, but they&#8217;ve got their work cut out. They&#8217;re under-funded and it&#8217;s slow progress.</p> <a href='http://www.flickr.com/photos/russss/sets/72157627355971457'> <img src='http://farm7.static.flickr.com/6079/6064070442_d9221d74ea_z.jpg' /> </a> <p>Inside, the forts are surreal and almost post-apocalyptic; crumbling into rust inside as well as out. All you can hear is the sea and the clanging of the bell in the nearby buoy which marks the edge of the shipping channel.</p> <p>Only one of the seven towers which make up Red Sands is currently accessible by boat, although they&#8217;re working on running a bridge over to the central control tower. Despite the rust, this fort is livable-in &#8212; people stay here during the yearly temporary radio broadcasts.</p> <a href='http://www.flickr.com/photos/russss/sets/72157627355971457'> <img src='http://farm7.static.flickr.com/6067/6063528323_97630e23dd_z.jpg' /> <img src='http://farm7.static.flickr.com/6189/6063521455_fb17396642_z.jpg' /> </a> <p>More photos in my <a href='http://www.flickr.com/photos/russss/sets/72157627355971457/'>Flickr stream</a>. Massive thanks to <a href='http://www.tomscott.com/'>Tom Scott</a> for organising and to <a href='http://www.project-redsand.com/'>Project Redsand</a> for letting us on the fort.</p> Sentimentality http://russ.garrett.co.uk/2011/07/21/sentimentality Thu, 21 Jul 2011 00:00:00 +0000 http://russ.garrett.co.uk/2011/07/21/sentimentality <p>The last space shuttle has <a href='http://www.bbc.co.uk/news/science-environment-14220423'>landed</a>. It&#8217;s sad, but not because America has lost its only way of putting humans into orbit; not because there&#8217;s no replacement ready (there wasn&#8217;t one when Apollo ended either).</p> <p>It&#8217;s sad because each shuttle wasn&#8217;t just a disposable machine like all its predecessors; it was a <em>ship</em>, and like earthbound ships each orbiter had a life &#8212; and a personality &#8212; which was contributed to by the thousands of people who flew them, worked with them, and followed their flights.</p> <p>So yes, I&#8217;m sad to see the shuttle go. It was a gigantic, expensive, dangerous, fantastically complex white elephant &#8212; a product of a government design process gone <a href='http://www.fotuva.org/feynman/challenger-appendix.html'>pathologically wrong</a>. But each shuttle had a life, and that&#8217;s what differentiated them from just another capsule.</p> <p>Perhaps one day we&#8217;ll be ready to try building a reusable craft which works, which actually makes economic sense, but until that day I think we&#8217;ll remember the shuttle more fondly than we did Apollo.</p> <a href='http://www.flickr.com/photos/nasahqphoto/5960203575/'><img alt='Atlantis lands for the final time' src='http://farm7.static.flickr.com/6126/5960203575_1bcf6f07aa.jpg' title='Atlantis lands for the final time' width='400' /></a> Little by Little… http://russ.garrett.co.uk/2011/02/20/little-by-little Sun, 20 Feb 2011 00:00:00 +0000 http://russ.garrett.co.uk/?p=203 <blockquote> <p>Little by little the machine will become part of humanity. Read the history of the railways in France, and doubtless elsewhere too: they had all the trouble in the world to tame the people of our villages. The locomotive was an iron monster. Time had to pass before men forgot what it was made of. Mysteriously, life began to run through it, and now it is wrinkled and old. What is it today for the villager except a humble friend who calls every evening at six?</p> <p>The sailing vessel itself was once a machine born of the calculations of engineers, yet it does not disturb our philosophers. The sloop took its place in the speech of men. There is a poetry of sailing as old as the world. There have always been seamen in recorded time. The man who assumes that there is an essential difference between the sloop and the airplane lacks historic perspective.</p> <p>Every machine will gradually take on this patina and lose its identity in its function.</p> </blockquote> <p>&#8212; Antoine de Saint-Exupéry, <em>Wind, Sand, and Stars</em></p> Tough and Competent http://russ.garrett.co.uk/2011/01/30/tough-and-competent Sun, 30 Jan 2011 00:00:00 +0000 http://russ.garrett.co.uk/?p=198 <p>On January 27th 1967, the <a href='http://en.wikipedia.org/wiki/Apollo_1'>Apollo 1</a> spacecraft suffered a catastrophic launchpad fire at Cape Canaveral, killing the three astronauts aboard. The following Monday, 44 years ago today, Gene Kranz gave this speech to his team:</p> <blockquote> <p>Spaceflight will never tolerate carelessness, incapacity, and neglect. Somewhere, somehow, we screwed up. It could have been in design, build, or test. Whatever it was, we should have caught it. We were too gung ho about the schedule and we locked out all of the problems we saw each day in our work.</p> <p>Every element of the program was in trouble and so were we. The simulators were not working, Mission Control was behind in virtually every area, and the flight and test procedures changed daily. Nothing we did had any shelf life. Not one of us stood up and said, &#8216;Dammit, stop!&#8217; I don&#8217;t know what Thompson&#8217;s committee will find as the cause, but I know what I find. We are the cause! We were not ready! We did not do our job. We were rolling the dice, hoping that things would come together by launch day, when in our hearts we knew it would take a miracle. We were pushing the schedule and betting that the Cape would slip before we did.</p> <p>From this day forward, Flight Control will be known by two words: &#8216;Tough&#8217; and &#8216;Competent.&#8217; Tough means we are forever accountable for what we do or what we fail to do. We will never again compromise our responsibilities. Every time we walk into Mission Control we will know what we stand for. Competent means we will never take anything for granted. We will never be found short in our knowledge and in our skills. Mission Control will be perfect.</p> <p>When you leave this meeting today you will go to your office and the first thing you will do there is to write &#8216;Tough and Competent&#8217; on your blackboards. It will never be erased. Each day when you enter the room these words will remind you of the price paid by Grissom, White, and Chaffee. These words are the price of admission to the ranks of Mission Control.</p> </blockquote> <p>Kranz was one of the unsung heroes of the space race, and the Apollo 1 incident in particular marks a transition from a sloppy, hurried engineering culture into the well-oiled machine which managed to bring the <a href='http://apollo13.spacelog.org'>Apollo 13</a> astronauts back alive.</p> Peer-to-Peer Lending with Zopa http://russ.garrett.co.uk/2010/11/08/peer-to-peer-lending-with-zopa Mon, 08 Nov 2010 00:00:00 +0000 http://russ.garrett.co.uk/?p=192 <p>I&#8217;ve been a user of <a href='http://uk.zopa.com/member/russss'>Zopa</a> for about 15 months now. If you&#8217;re not aware of it, Zopa is a peer-to-peer lending exchange: they&#8217;re a market where people who need money can borrow it from people who can lend it. (Working for a <a href='http://smarkets.com'>different kind of exchange</a> myself, I like the concept.)</p> <p>I spent some time working out how much interest that Zopa has been paying me since I joined last August. One of their downsides is that they don&#8217;t seem to make it very clear what annualized interest you&#8217;ve actually been earning.</p> <p>It turns out that I&#8217;ve been paid slightly more than <strong>7.4%</strong> AER, net of bad debt (there was none) and Zopa&#8217;s 1% annual fee. During that time, the Bank of England base rate has been 0.5% and the best savings rate has barely brushed 3.5% on average.</p> <p>Of course, the FTSE All-Share made 22% AER over the same period, but Zopa is still a nice fixed(ish)-income way of diversifying.</p> <p>One thing which is a bit annoying with Zopa is that you have to constantly adjust your offer price to follow the market and get the best rate for your money (which can be coming back in as repayments from other borrowers). Zopa has 5 different creditworthiness markets with different prices, and I worry about ending up with skewed exposure towards one category. It would be great if Zopa had an API so I could adjust my prices automatically.</p> Laser Etching my Macbook Pro with Science http://russ.garrett.co.uk/2010/10/18/laser-etching-my-macbook-pro-with-science Mon, 18 Oct 2010 00:00:00 +0000 http://russ.garrett.co.uk/?p=183 <p>At <a href='http://london.hackspace.org.uk'>London Hackspace</a> we got a <a href='http://wiki.hackspace.org.uk/wiki/Equipment/LaserCutter'>laser cutter</a> a month or two ago. Despite being cheap (for a laser cutter) and of anonymous Chinese provenance, it&#8217;s pretty reliable, very useful, and great fun to use.</p> <p>I recently got a new Macbook Pro, and I was thinking of what to etch the lid with. (Most metals won&#8217;t etch &#8212; let alone cut &#8212; on our 40W laser cutter, but anodized aluminium is the exception.) Laser cutters work best with vector drawings; you can etch raster images but on our machine this leaves a &#8220;shadow&#8221; on anodized aluminium due to the laser starting up.</p> <p>So I wanted a cool vector image to etch my laptop with, and I decided to use a picture of a particle collision in a bubble chamber. I found <a href='http://teachers.web.cern.ch/teachers/archiv/HST2001/bubblechambers/Dalitz.gif'>this image</a> and traced it using Inkscape. I&#8217;m pretty happy with the result.</p> <a href='http://www.flickr.com/photos/russss/5091497832/in/photostream/'><img alt='Laser Etched Macbook' class='aligncenter' src='http://farm5.static.flickr.com/4151/5091497832_cd1f0641fc_z.jpg' title='Laser Etched Macbook' width='550' /></a><a href='http://www.flickr.com/photos/russss/sets/72157625185890990/'>More photos</a>