diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2011, Joseph Adams
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Joseph Adams nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,75 @@
+import Prelude hiding (catch)
+
+import Stat
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad.CryptoRandom
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Error
+import Crypto.Random
+import System.Directory
+import System.Environment
+import System.Exit
+import System.FilePath
+import System.IO
+
+-- | Handle an IOException by returning the given value instead.
+onIOException :: a -> IO a -> IO a
+onIOException fallback_result action =
+        action `catch` handler fallback_result
+    where
+        handler :: a -> IOException -> IO a
+        handler x _ = return x
+
+notSpecial :: FilePath -> Bool
+notSpecial name = name /= "." && name /= ".."
+
+listFiles :: FilePath -> IO [FilePath]
+listFiles dir = onIOException []
+              $ map (dir </>) . filter notSpecial <$> getDirectoryContents dir
+
+randFile :: (CryptoRandomGen g, Error e, ContainsGenError e)
+         => FilePath
+         -> CRandT g e IO (Maybe FilePath)
+randFile dir = lift (listFiles dir) >>= pickFromList where
+    pickFromList list =
+        if null list
+            then return Nothing
+            else do
+                let len = length list
+                idx  <- getCRandomR (0, len-1)
+                let path = list !! idx
+
+                st <- lift (stat path)
+                case st of
+                    File      -> return (Just path)
+                    Directory -> do
+                        r <- randFile path
+                        case r of
+                            Just _  -> return r
+                            Nothing -> pickFromList (list `without` idx)
+                    Other     -> pickFromList (list `without` idx)
+    without list idx = as ++ bs where
+        (as, _:bs) = splitAt idx list
+
+-- | Like 'runCRandT', but discard the generator and throw the error
+-- (if necessary).
+runCRandT_ :: CRandT g GenError IO a -> g -> IO a
+runCRandT_ action gen = do
+    res <- runCRandT action gen
+    case res of
+        Left e       -> throwIO $ userError $ show e
+        Right (a, _) -> return a
+
+main :: IO ()
+main = do
+    g <- newGenIO :: IO SystemRandom
+    file <- runCRandT_ (randFile ".") g
+    case file of
+        Just f  -> putStrLn $ makeRelative "." $ f
+        Nothing -> do
+            progName <- getProgName
+            hPutStrLn stderr $
+                progName ++ ": Current directory does not contain any files"
+            exitFailure
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Stat.hs b/Stat.hs
new file mode 100644
--- /dev/null
+++ b/Stat.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE CPP #-}
+-- | Portably test if a given path is a file, directory, or something else.
+module Stat (
+    Stat(..)
+  , stat
+) where
+
+import Prelude hiding (catch)
+import Control.Applicative
+import Control.Exception
+
+#ifdef mingw32_HOST_OS
+import Data.Bits
+import qualified System.Win32 as Win32
+#else
+import qualified System.Posix as Posix
+#endif
+
+data Stat = File | Directory | Other
+
+stat :: FilePath -> IO Stat
+stat path = stat_ path `catch` handler
+    where
+        handler :: IOException -> IO Stat
+        handler _ = return Other
+
+stat_ :: FilePath -> IO Stat
+
+#ifdef mingw32_HOST_OS
+stat_ path = decode <$> Win32.getFileAttributes path
+    where
+        decode flags | flags .&. Win32.fILE_ATTRIBUTE_DIRECTORY /= 0 = Directory
+                        -- TODO: Handle non-file non-directory files properly
+                     | otherwise = File
+#else
+stat_ path = decode <$> Posix.getFileStatus path
+    where
+        decode s | Posix.isRegularFile s  = File
+                 | Posix.isDirectory s    = Directory
+                 | otherwise              = Other
+#endif
diff --git a/randfile.cabal b/randfile.cabal
new file mode 100644
--- /dev/null
+++ b/randfile.cabal
@@ -0,0 +1,47 @@
+name:                randfile
+version:             0.1.0.0
+synopsis:            Program for picking a random file
+description:
+    This program selects a random file from the current directory, including
+    files in subdirectories.  If you like to manage your music collection using
+    the command line, this program can be used to approximate \"shuffle\".
+    .
+    More precisely, it starts at the current directory, picks a random entry,
+    and descends if it is a directory.  It does not give more weight to
+    directories containing more files.  It should only fail if there are no
+    regular files under the current directory that you can see.
+    .
+    If you are wondering why I bothered to use crypto-grade random number
+    generation, it is because I found @System.Random@ to be unsatisfactory.
+    With @System.Random@, this program tended to return the same file
+    repeatedly.
+    .
+    Note: on Unix, this program follows symbolic links, while on Windows, it
+    doesn't.  Symbolic links were introduced in Windows Vista and
+    Windows Server 2008.
+license:             BSD3
+license-file:        LICENSE
+author:              Joey Adams
+maintainer:          joeyadams3.14159@gmail.com
+copyright:           Copyright (c) Joseph Adams 2011
+category:            System
+build-type:          Simple
+cabal-version:       >=1.8
+
+executable randfile
+    main-is:         Main.hs
+    other-modules:
+        Stat
+    build-depends:   base == 4.*
+                   , filepath
+                   , directory
+                   , crypto-api
+                   , transformers
+                   , monadcryptorandom
+    if os(windows) {
+        build-depends: Win32
+    } else {
+        build-depends: unix
+    }
+    other-extensions:   CPP
+    ghc-options:     -Wall
