diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,34 @@
+## Version 0.7.4.1 (2025-08-26)
+
+- Fix build with `-Werror=unused-packages`.
+- Tested with GHC 8.0 - 9.14 alpha1.
+
+## Version 0.7.4 (2025-03-27)
+
+- Add `wasm32-wasi` support
+  ([PR #16](https://github.com/haskell-pkg-janitors/unix-compat/pull/16)).
+- Tested with GHC 8.0 - 9.12.
+
+## Version 0.7.3 (2024-10-11)
+
+- Fix `sysmacros.h` include for GNU/Hurd
+  ([PR #12](https://github.com/haskell-pkg-janitors/unix-compat/pull/12)).
+- Tested with GHC 8.0 - 9.10.
+
+## Version 0.7.2 (2024-06-25)
+
+- Remove flag `old-time` and drop support for `old-time`.
+- Remove support for GHC 7.
+- Tested with GHC 8.0 - 9.10.
+
+## Version 0.7.1 (2023-12-06) Santa Clause edition
+
+- Add `System.PosixCompat.Process` module, exporting `getProcessID`.
+
+## Version 0.7 (2023-03-15)
+
+- Remove `System.PosixCompat.User` module.
+
+## Version 0.6 (2022-05-22)
+
+- Better support for symbolic links.
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,5 @@
+BSD 3-Clause License
+
 Copyright (c) 2007-2008, Björn Bringert
 Copyright (c) 2007-2009, Duncan Coutts
 Copyright (c) 2010-2011, Jacob Stanley
diff --git a/cbits/HsUnixCompat.c b/cbits/HsUnixCompat.c
--- a/cbits/HsUnixCompat.c
+++ b/cbits/HsUnixCompat.c
@@ -2,7 +2,7 @@
 
 #ifdef SOLARIS
 #include <sys/mkdev.h>
-#elif defined(__linux__)
+#elif defined(__linux__) || defined(__GNU__)
 #include <sys/sysmacros.h>
 #endif
 
diff --git a/cbits/mktemp.c b/cbits/mktemp.c
--- a/cbits/mktemp.c
+++ b/cbits/mktemp.c
@@ -41,13 +41,21 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+
+#if defined(__wasm__)
+#include <sys/random.h>
+#else
 #include <windows.h>
 #include <wincrypt.h>
 
-static int random(uint32_t *);
+#define open _open
+#define stat _stat
+#endif
+
+static int unixcompat_random(uint32_t *);
 static int _gettemp(char *, int *);
 
-static const unsigned char padchar[] =
+static const char padchar[] =
 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
 int unixcompat_mkstemp(char *path)
@@ -64,7 +72,7 @@
 {
     char *start, *trv, *suffp, *carryp;
     char *pad;
-    struct _stat sbuf;
+    struct stat sbuf;
     int rval;
     uint32_t randidx, randval;
     char carrybuf[MAXPATHLEN];
@@ -84,7 +92,7 @@
 
     /* Fill space with random characters */
     while (trv >= path && *trv == 'X') {
-        if (!random(&randval)) {
+        if (!unixcompat_random(&randval)) {
             /* this should never happen */
             errno = EIO;
             return 0;
@@ -104,7 +112,7 @@
         for (; trv > path; --trv) {
             if (*trv == '/') {
                 *trv = '\0';
-                rval = _stat(path, &sbuf);
+                rval = stat(path, &sbuf);
                 *trv = '/';
                 if (rval != 0)
                     return (0);
@@ -120,11 +128,11 @@
     for (;;) {
         if (doopen) {
             if ((*doopen =
-                _open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0)
+                open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0)
                 return (1);
             if (errno != EEXIST)
                 return (0);
-        } else if (_stat(path, &sbuf))
+        } else if (stat(path, &sbuf))
             return (errno == ENOENT);
 
         /* If we have a collision, cycle through the space of filenames */
@@ -154,8 +162,15 @@
     /*NOTREACHED*/
 }
 
-static int random(uint32_t *value)
+#if defined(__wasm__)
+static int unixcompat_random(uint32_t *value)
 {
+    int r = getentropy(value, sizeof(uint32_t));
+    return r == 0 ? 1 : 0;
+}
+#else
+static int unixcompat_random(uint32_t *value)
+{
     /* This handle is never released. Windows will clean up when the process
      * exits. Python takes this approach when emulating /dev/urandom, and if
      * it's good enough for them, then it's good enough for us. */
@@ -171,3 +186,4 @@
 
     return 1;
 }
+#endif
diff --git a/include/HsUnixCompat.h b/include/HsUnixCompat.h
--- a/include/HsUnixCompat.h
+++ b/include/HsUnixCompat.h
@@ -4,5 +4,3 @@
 unsigned int unix_major(dev_t dev);
 unsigned int unix_minor(dev_t dev);
 dev_t unix_makedev(unsigned int maj, unsigned int min);
-
-#define NEED_setSymbolicLinkOwnerAndGroup !HAVE_LCHOWN
diff --git a/src/System/PosixCompat.hs b/src/System/PosixCompat.hs
--- a/src/System/PosixCompat.hs
+++ b/src/System/PosixCompat.hs
@@ -7,20 +7,20 @@
 -}
 module System.PosixCompat (
       module System.PosixCompat.Files
+    , module System.PosixCompat.Process
     , module System.PosixCompat.Temp
     , module System.PosixCompat.Time
     , module System.PosixCompat.Types
     , module System.PosixCompat.Unistd
-    , module System.PosixCompat.User
     , usingPortableImpl
     ) where
 
 import System.PosixCompat.Files
+import System.PosixCompat.Process
 import System.PosixCompat.Temp
 import System.PosixCompat.Time
 import System.PosixCompat.Types
 import System.PosixCompat.Unistd
-import System.PosixCompat.User
 
 -- | 'True' if unix-compat is using its portable implementation,
 --   or 'False' if the unix package is simply being re-exported.
diff --git a/src/System/PosixCompat/Extensions.hsc b/src/System/PosixCompat/Extensions.hsc
--- a/src/System/PosixCompat/Extensions.hsc
+++ b/src/System/PosixCompat/Extensions.hsc
@@ -12,7 +12,7 @@
     ) where
 
 
-#ifndef mingw32_HOST_OS
+#if !(defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH))
 #include "HsUnixCompat.h"
 #endif
 
@@ -27,7 +27,7 @@
 --
 -- The portable implementation always returns @0@.
 deviceMajor :: DeviceID -> CMajor
-#ifdef mingw32_HOST_OS
+#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)
 deviceMajor _ = 0
 #else
 deviceMajor dev = unix_major dev
@@ -39,7 +39,7 @@
 --
 -- The portable implementation always returns @0@.
 deviceMinor :: DeviceID -> CMinor
-#ifdef mingw32_HOST_OS
+#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)
 deviceMinor _ = 0
 #else
 deviceMinor dev = unix_minor dev
@@ -49,7 +49,7 @@
 
 -- | Creates a 'DeviceID' for a device file given a major and minor number.
 makeDeviceID :: CMajor -> CMinor -> DeviceID
-#ifdef mingw32_HOST_OS
+#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)
 makeDeviceID _ _ = 0
 #else
 makeDeviceID ma mi = unix_makedev ma mi
diff --git a/src/System/PosixCompat/Files.hsc b/src/System/PosixCompat/Files.hsc
--- a/src/System/PosixCompat/Files.hsc
+++ b/src/System/PosixCompat/Files.hsc
@@ -106,11 +106,11 @@
 
 #ifndef mingw32_HOST_OS
 
-#include "HsUnixCompat.h"
+#include "HsUnixConfig.h"
 
 import System.Posix.Files
 
-#if NEED_setSymbolicLinkOwnerAndGroup
+#if !HAVE_LCHOWN
 import System.PosixCompat.Types
 
 setSymbolicLinkOwnerAndGroup :: FilePath -> UserID -> GroupID -> IO ()
@@ -122,6 +122,7 @@
 import Control.Exception (bracket)
 import Control.Monad (liftM, liftM2)
 import Data.Bits ((.|.), (.&.))
+import Data.Char (toLower)
 import Data.Int (Int64)
 import Data.Time.Clock.POSIX (POSIXTime)
 import Foreign.C.Types (CTime(..))
@@ -133,10 +134,12 @@
 import System.Directory (executable, setOwnerExecutable)
 import System.Directory (searchable, setOwnerSearchable)
 import System.Directory (doesFileExist, doesDirectoryExist)
+import System.Directory (getSymbolicLinkTarget)
+import System.FilePath (takeExtension)
 import System.IO (IOMode(..), openFile, hSetFileSize, hClose)
 import System.IO.Error
 import System.PosixCompat.Types
-import System.Win32.File hiding (getFileType)
+import System.Win32.File
 import System.Win32.HardLink (createHardLink)
 import System.Win32.Time (FILETIME(..), getFileTime, setFileTime)
 import System.Win32.Types (HANDLE)
@@ -314,14 +317,23 @@
 isSocket stat =
     (fileMode stat `intersectFileModes` fileTypeModes) == socketMode
 
-getFileStatus :: FilePath -> IO FileStatus
-getFileStatus path = do
-    perm  <- liftM permsToMode (getPermissions path)
-    typ   <- getFileType path
+getStatus :: Bool -> FilePath -> IO FileStatus
+getStatus forLink path = do
     info  <- bracket openPath closeHandle getFileInformationByHandle
     let atime = windowsToPosixTime (bhfiLastAccessTime info)
         mtime = windowsToPosixTime (bhfiLastWriteTime info)
         ctime = windowsToPosixTime (bhfiCreationTime info)
+        attr = bhfiFileAttributes info
+        isLink = attr .&. fILE_ATTRIBUTE_REPARSE_POINT /= 0
+        isDir = attr .&. fILE_ATTRIBUTE_DIRECTORY /= 0
+        isWritable = attr .&. fILE_ATTRIBUTE_READONLY == 0
+        -- Contrary to Posix systems, directory symlinks on Windows have both
+        -- fILE_ATTRIBUTE_REPARSE_POINT and fILE_ATTRIBUTE_DIRECTORY bits set.
+        typ
+          | isLink = symbolicLinkMode
+          | isDir = directoryMode
+          | otherwise = regularFileMode -- it's a lie but what can we do?
+        perm = permissions path isWritable isDir
     return $ FileStatus
              { deviceID         = fromIntegral (bhfiVolumeSerialNumber info)
              , fileID           = fromIntegral (bhfiFileIndex info)
@@ -340,13 +352,38 @@
              }
   where
     openPath = createFile path
-                 gENERIC_READ
+                 fILE_READ_EA
                  (fILE_SHARE_READ .|. fILE_SHARE_WRITE .|. fILE_SHARE_DELETE)
                  Nothing
                  oPEN_EXISTING
-                 (sECURITY_ANONYMOUS .|. fILE_FLAG_BACKUP_SEMANTICS)
+                 (fILE_FLAG_BACKUP_SEMANTICS .|. openReparsePoint)
                  Nothing
 
+    openReparsePoint = if forLink then fILE_FLAG_OPEN_REPARSE_POINT else 0
+
+    -- not yet defined in Win32 package:
+    fILE_FLAG_OPEN_REPARSE_POINT :: FileAttributeOrFlag
+    fILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
+
+    -- Fused from System.Directory.Internal.Windows.getAccessPermissions
+    -- and the former modeToPerms function.
+    permissions path is_writable is_dir = r .|. w .|. x
+      where
+        is_executable =
+          (toLower <$> takeExtension path) `elem` [".bat", ".cmd", ".com", ".exe"]
+        r = ownerReadMode .|. groupReadMode .|. otherReadMode
+        w = f is_writable (ownerWriteMode .|. groupWriteMode .|. otherWriteMode)
+        x = f (is_executable || is_dir)
+              (ownerExecuteMode .|. groupExecuteMode .|. otherExecuteMode)
+        f True m  = m
+        f False _ = nullFileMode
+
+getSymbolicLinkStatus :: FilePath -> IO FileStatus
+getSymbolicLinkStatus = getStatus True
+
+getFileStatus :: FilePath -> IO FileStatus
+getFileStatus = getStatus False
+
 -- | Convert a 'POSIXTime' (synomym for 'Data.Time.Clock.NominalDiffTime')
 -- into an 'EpochTime' (integral number of seconds since epoch). This merely
 -- throws away the fractional part.
@@ -373,30 +410,9 @@
   truncate (t * 10000000 + windowsPosixEpochDifference)
 -}
 
-permsToMode :: Permissions -> FileMode
-permsToMode perms = r .|. w .|. x
-  where
-    r = f (readable perms) (ownerReadMode .|. groupReadMode .|. otherReadMode)
-    w = f (writable perms) (ownerWriteMode .|. groupWriteMode .|. otherWriteMode)
-    x = f (executable perms || searchable perms)
-          (ownerExecuteMode .|. groupExecuteMode .|. otherExecuteMode)
-    f True m  = m
-    f False _ = nullFileMode
-
-getFileType :: FilePath -> IO FileMode
-getFileType path =
-    do f <- doesFileExist path
-       if f then return regularFileMode
-            else do d <- doesDirectoryExist path
-                    if d then return directoryMode
-                         else unsupported "Unknown file type."
-
 getFdStatus :: Fd -> IO FileStatus
 getFdStatus _ = unsupported "getFdStatus"
 
-getSymbolicLinkStatus :: FilePath -> IO FileStatus
-getSymbolicLinkStatus path = getFileStatus path
-
 createNamedPipe :: FilePath -> FileMode -> IO ()
 createNamedPipe _ _ = unsupported "createNamedPipe"
 
@@ -419,7 +435,7 @@
 createSymbolicLink _ _ = unsupported "createSymbolicLink"
 
 readSymbolicLink :: FilePath -> IO FilePath
-readSymbolicLink _ = unsupported "readSymbolicLink"
+readSymbolicLink = getSymbolicLinkTarget
 
 -- -----------------------------------------------------------------------------
 -- Renaming
diff --git a/src/System/PosixCompat/Internal/Time.hs b/src/System/PosixCompat/Internal/Time.hs
--- a/src/System/PosixCompat/Internal/Time.hs
+++ b/src/System/PosixCompat/Internal/Time.hs
@@ -10,16 +10,6 @@
     ) where
 
 import System.Posix.Types (EpochTime)
-
-#ifdef OLD_TIME
-
-import System.Time (ClockTime(TOD), getClockTime)
-
-clockTimeToEpochTime :: ClockTime -> EpochTime
-clockTimeToEpochTime (TOD s _) = fromInteger s
-
-#else
-
 import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
 
 type ClockTime = POSIXTime
@@ -29,5 +19,3 @@
 
 clockTimeToEpochTime :: ClockTime -> EpochTime
 clockTimeToEpochTime = fromInteger . floor
-
-#endif
diff --git a/src/System/PosixCompat/Process.hs b/src/System/PosixCompat/Process.hs
new file mode 100644
--- /dev/null
+++ b/src/System/PosixCompat/Process.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE CPP #-}
+
+{-|
+This module intends to make the operations of @System.Posix.Process@ available
+on all platforms.
+-}
+module System.PosixCompat.Process (
+      getProcessID
+    ) where
+
+#if defined(mingw32_HOST_OS)
+
+import System.Posix.Types (ProcessID)
+import System.Win32.Process (getCurrentProcessId)
+
+getProcessID :: IO ProcessID
+getProcessID = fromIntegral <$> getCurrentProcessId
+
+#elif defined(wasm32_HOST_ARCH)
+
+import System.Posix.Types (ProcessID)
+
+getProcessID :: IO ProcessID
+getProcessID = pure 1
+
+#else
+
+import System.Posix.Process
+
+#endif
diff --git a/src/System/PosixCompat/Temp.hs b/src/System/PosixCompat/Temp.hs
--- a/src/System/PosixCompat/Temp.hs
+++ b/src/System/PosixCompat/Temp.hs
@@ -11,13 +11,13 @@
       mkstemp
     ) where
 
-#ifndef mingw32_HOST_OS
+#if !(defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH))
 -- Re-export unix package
 
 import System.Posix.Temp
 
 #elif defined(__GLASGOW_HASKELL__)
--- Windows w/ GHC, we have fdToHandle so we
+-- Window/WASM w/ GHC, we have fdToHandle so we
 -- can use our own implementation of mkstemp.
 
 import System.IO (Handle)
diff --git a/src/System/PosixCompat/User.hsc b/src/System/PosixCompat/User.hsc
deleted file mode 100644
--- a/src/System/PosixCompat/User.hsc
+++ /dev/null
@@ -1,133 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-{-|
-This module makes the operations exported by @System.Posix.User@
-available on all platforms. On POSIX systems it re-exports operations from
-@System.Posix.User@. On other platforms it provides dummy implementations.
--}
-module System.PosixCompat.User (
-    -- * User environment
-    -- ** Querying the user environment
-      getRealUserID
-    , getRealGroupID
-    , getEffectiveUserID
-    , getEffectiveGroupID
-    , getGroups
-    , getLoginName
-    , getEffectiveUserName
-
-    -- *** The group database
-    , GroupEntry(..)
-    , getGroupEntryForID
-    , getGroupEntryForName
-    , getAllGroupEntries
-
-    -- *** The user database
-    , UserEntry(..)
-    , getUserEntryForID
-    , getUserEntryForName
-    , getAllUserEntries
-
-    -- ** Modifying the user environment
-    , setUserID
-    , setGroupID
-    ) where
-
-#ifndef mingw32_HOST_OS
-
-#include "HsUnixCompat.h"
-
-import System.Posix.User
-
-#if __GLASGOW_HASKELL__<605
-getAllGroupEntries :: IO [GroupEntry]
-getAllGroupEntries = return []
-
-getAllUserEntries :: IO [UserEntry]
-getAllUserEntries = return []
-#endif
-
-#else /* Portable implementation */
-
-import System.IO.Error
-import System.PosixCompat.Types
-
-unsupported :: String -> IO a
-unsupported f = ioError $ mkIOError illegalOperationErrorType x Nothing Nothing
-    where x = "System.PosixCompat.User." ++ f ++ ": not supported"
-
--- -----------------------------------------------------------------------------
--- User environment
-
-getRealUserID :: IO UserID
-getRealUserID = unsupported "getRealUserID"
-
-getRealGroupID :: IO GroupID
-getRealGroupID = unsupported "getRealGroupID"
-
-getEffectiveUserID :: IO UserID
-getEffectiveUserID = unsupported "getEffectiveUserID"
-
-getEffectiveGroupID :: IO GroupID
-getEffectiveGroupID = unsupported "getEffectiveGroupID"
-
-getGroups :: IO [GroupID]
-getGroups = return []
-
-getLoginName :: IO String
-getLoginName = unsupported "getLoginName"
-
-setUserID :: UserID -> IO ()
-setUserID _ = return ()
-
-setGroupID :: GroupID -> IO ()
-setGroupID _ = return ()
-
--- -----------------------------------------------------------------------------
--- User names
-
-getEffectiveUserName :: IO String
-getEffectiveUserName = unsupported "getEffectiveUserName"
-
--- -----------------------------------------------------------------------------
--- The group database
-
-data GroupEntry = GroupEntry
-    { groupName     :: String
-    , groupPassword :: String
-    , groupID       :: GroupID
-    , groupMembers  :: [String]
-    } deriving (Show, Read, Eq)
-
-getGroupEntryForID :: GroupID -> IO GroupEntry
-getGroupEntryForID _ = unsupported "getGroupEntryForID"
-
-getGroupEntryForName :: String -> IO GroupEntry
-getGroupEntryForName _ = unsupported "getGroupEntryForName"
-
-getAllGroupEntries :: IO [GroupEntry]
-getAllGroupEntries = return []
-
--- -----------------------------------------------------------------------------
--- The user database (pwd.h)
-
-data UserEntry = UserEntry
-    { userName      :: String
-    , userPassword  :: String
-    , userID        :: UserID
-    , userGroupID   :: GroupID
-    , userGecos     :: String
-    , homeDirectory :: String
-    , userShell     :: String
-    } deriving (Show, Read, Eq)
-
-getUserEntryForID :: UserID -> IO UserEntry
-getUserEntryForID _ = unsupported "getUserEntryForID"
-
-getUserEntryForName :: String -> IO UserEntry
-getUserEntryForName _ = unsupported "getUserEntryForName"
-
-getAllUserEntries :: IO [UserEntry]
-getAllUserEntries = return []
-
-#endif
diff --git a/tests/LinksSpec.hs b/tests/LinksSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/LinksSpec.hs
@@ -0,0 +1,122 @@
+module LinksSpec(linksSpec) where
+
+import Control.Concurrent ( threadDelay )
+import Control.Exception ( finally )
+import qualified System.Directory as D
+import System.Info ( os )
+import System.IO.Error ( tryIOError )
+import System.IO.Temp
+import System.PosixCompat
+import Test.Hspec
+import Test.HUnit
+
+isWindows :: Bool
+isWindows = os == "mingw32"
+
+linksSpec :: Spec
+linksSpec = do
+  describe "createSymbolicLink" $ do
+    it "should error on Windows and succeed on other OSes" $ do
+      runInTempDir $ do
+        writeFile "file" ""
+        result <- tryIOError $ createSymbolicLink "file" "file_link"
+        case result of
+          Left _  | isWindows -> return ()
+          Right _ | isWindows -> do
+            assertFailure "Succeeded while expected to fail on Windows"
+          Left e              -> assertFailure $ "Expected to succeed, but failed with " ++ show e
+          Right _             -> return ()
+  describe "getSymbolicLinkStatus" $ do
+    it "should detect symbolic link to a file" $ do
+      runFileLinkTest $ do
+        stat <- getSymbolicLinkStatus "file_link"
+        assert $ isSymbolicLink stat
+    it "should detect symbolic link to a directory" $ do
+      runDirLinkTest $ do
+        stat <- getSymbolicLinkStatus "dir_link"
+        assert $ isSymbolicLink stat
+    it "should give later time stamp than getFileStatus for link to file" $ do
+      runFileLinkTest $ do
+        lstat_mtime <- modificationTimeHiRes <$> getSymbolicLinkStatus "file_link"
+        stat_mtime <- modificationTimeHiRes <$> getFileStatus "file_link"
+        assert $ lstat_mtime > stat_mtime
+    it "should give later time stamp than getFileStatus for link to dir" $ do
+      runDirLinkTest $ do
+        lstat_mtime <- modificationTimeHiRes <$> getSymbolicLinkStatus "dir_link"
+        stat_mtime <- modificationTimeHiRes <$> getFileStatus "dir_link"
+        assert $ lstat_mtime > stat_mtime
+    it "should give a different fileID than getFileStatus for link to file" $ do
+      runFileLinkTest $ do
+        lstat_id <- fileID <$> getSymbolicLinkStatus "file_link"
+        fstat_id <- fileID <$> getFileStatus "file_link"
+        assert $ lstat_id /= fstat_id
+    it "should give a different fileID than getFileStatus for link to dir" $ do
+      runDirLinkTest $ do
+        lstat_id <- fileID <$> getSymbolicLinkStatus "dir_link"
+        fstat_id <- fileID <$> getFileStatus "dir_link"
+        assert $ lstat_id /= fstat_id
+  describe "getFileStatus" $ do
+    it "should detect that symbolic link target is a file" $ do
+      runFileLinkTest $ do
+        stat <- getFileStatus "file_link"
+        assert $ isRegularFile stat
+    it "should detect that symbolic link target is a directory" $ do
+      runDirLinkTest $ do
+        stat <- getFileStatus "dir_link"
+        assert $ isDirectory stat
+    it "should be equal for link and link target (except access time)" $ do
+      runFileLinkTest $ do
+        fstat <- getFileStatus "file"
+        flstat <- getFileStatus "file_link"
+        assert $ fstat `mostlyEq` flstat
+      runDirLinkTest $ do
+        fstat <- getFileStatus "dir"
+        flstat <- getFileStatus "dir_link"
+        assert $ fstat `mostlyEq` flstat
+
+  where
+
+    runFileLinkTest action =
+      runInTempDir $ do
+        writeFile "file" ""
+        threadDelay delay
+        D.createFileLink "file" "file_link"
+        action
+
+    runDirLinkTest action =
+      runInTempDir $ do
+        D.createDirectory "dir"
+        threadDelay delay
+        D.createDirectoryLink "dir" "dir_link"
+        action
+
+    runInTempDir action = do
+      orig <- D.getCurrentDirectory
+      withTempDirectory orig "xxxxxxx" $ \tmp -> do
+        D.setCurrentDirectory tmp
+        action `finally` D.setCurrentDirectory orig
+
+    -- We need to set the delay this high because otherwise the timestamp test
+    -- above fails on Linux and Windows, though not on MacOS. This seems to be
+    -- an artefact of the GHC runtime system which gives two subsequently
+    -- created files the same timestamp unless the delay is large enough.
+    delay = 10000
+
+    -- Test equality for all parts except accessTime
+    mostlyEq :: FileStatus -> FileStatus -> Bool
+    mostlyEq x y = tuple x == tuple y
+      where
+        tuple s =
+          ( deviceID s
+          , fileID s
+          , fileMode s
+          , linkCount s
+          , fileOwner s
+          , fileGroup s
+          , specialDeviceID s
+          , fileSize s
+          , modificationTime s
+          , statusChangeTime s
+          , modificationTimeHiRes s
+          , statusChangeTimeHiRes s
+          )
diff --git a/tests/MkstempSpec.hs b/tests/MkstempSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/MkstempSpec.hs
@@ -0,0 +1,29 @@
+module MkstempSpec(mkstempSpec) where
+import Control.Monad.Parallel
+import System.Directory
+import System.IO
+import System.PosixCompat ( mkstemp )
+import Test.Hspec
+
+mkstempSpec :: Spec
+mkstempSpec = describe "mkstemp" $ do
+    it "TODO" $ do
+        let n = 10000
+        hSetBuffering stdout NoBuffering
+
+        putStr $ "Creating " ++ show n ++ " temp files..."
+        xs <- replicateM n createTempFile
+        if length xs == n
+        then putStrLn "ok"
+        else putStrLn "FAIL"
+
+        putStr "Deleting temp files..."
+        Control.Monad.Parallel.mapM_ removeFile xs
+        putStrLn "ok"
+
+createTempFile :: IO FilePath
+createTempFile = do
+    (p,h) <- mkstemp "tempfileXXXXXXX"
+    hPutStrLn h "this is a temporary file"
+    hClose h
+    return p
diff --git a/tests/ProcessSpec.hs b/tests/ProcessSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/ProcessSpec.hs
@@ -0,0 +1,12 @@
+module ProcessSpec (processSpec) where
+
+import System.PosixCompat
+import Test.HUnit
+import Test.Hspec
+
+processSpec :: Spec
+processSpec = do
+  describe "getProcessID" $ do
+    it "should work on Windows and Unix" $ do
+      pid <- getProcessID
+      assert $ pid > 0
diff --git a/tests/main.hs b/tests/main.hs
new file mode 100644
--- /dev/null
+++ b/tests/main.hs
@@ -0,0 +1,13 @@
+module Main where
+
+import MkstempSpec
+import LinksSpec
+import ProcessSpec
+
+import Test.Hspec
+
+main :: IO ()
+main = hspec $ do
+    mkstempSpec
+    linksSpec
+    processSpec
diff --git a/unix-compat.cabal b/unix-compat.cabal
--- a/unix-compat.cabal
+++ b/unix-compat.cabal
@@ -1,73 +1,110 @@
+cabal-version:  1.18
 name:           unix-compat
-version:        0.5.4
+version:        0.7.4.1
 synopsis:       Portable POSIX-compatibility layer.
 description:    This package provides portable implementations of parts
                 of the unix package. This package re-exports the unix
                 package when available. When it isn't available,
                 portable implementations are used.
 
-homepage:       http://github.com/jacobstanley/unix-compat
+homepage:       https://github.com/haskell-pkg-janitors/unix-compat
 license:        BSD3
 license-file:   LICENSE
 author:         Björn Bringert, Duncan Coutts, Jacob Stanley, Bryan O'Sullivan
-maintainer:     Jacob Stanley <jacob@stanley.io>
+maintainer:     https://github.com/haskell-pkg-janitors
 category:       System
 build-type:     Simple
-cabal-version:  >= 1.10
 
+tested-with:
+  GHC == 9.14.1
+  GHC == 9.12.2
+  GHC == 9.10.2
+  GHC == 9.8.4
+  GHC == 9.6.7
+  GHC == 9.4.8
+  GHC == 9.2.8
+  GHC == 9.0.2
+  GHC == 8.10.7
+  GHC == 8.8.4
+  GHC == 8.6.5
+  GHC == 8.4.4
+  GHC == 8.2.2
+  GHC == 8.0.2
+
+extra-doc-files:
+  CHANGELOG.md
+
 source-repository head
   type:     git
-  location: git://github.com/jacobstanley/unix-compat.git
-
-flag old-time
-  description: build against old-time package
-  default: False
+  location: https://github.com/haskell-pkg-janitors/unix-compat.git
 
 Library
-  default-language: Haskell2010
   hs-source-dirs: src
-  ghc-options: -Wall
-  build-depends: base == 4.*
 
   exposed-modules:
     System.PosixCompat
     System.PosixCompat.Extensions
     System.PosixCompat.Files
+    System.PosixCompat.Process
     System.PosixCompat.Temp
     System.PosixCompat.Time
     System.PosixCompat.Types
     System.PosixCompat.Unistd
-    System.PosixCompat.User
 
+  build-depends: base >= 4.9 && < 5
+
   if os(windows)
     c-sources:
       cbits/HsUname.c
       cbits/mktemp.c
 
     extra-libraries: msvcrt
-    build-depends: Win32 >= 2.5.0.0
-
-    if flag(old-time)
-      build-depends: old-time >= 1.0.0.0 && < 1.2.0.0
-      cpp-options: -DOLD_TIME
-
-      if impl(ghc < 7)
-        build-depends: directory == 1.0.*
-        cpp-options: -DDIRECTORY_1_0
-      else
-        build-depends: directory == 1.1.*
-    else
-      build-depends: time >= 1.0 && < 1.13
-      build-depends: directory >= 1.2 && < 1.4
+    build-depends: Win32     >= 2.5.0.0  && < 3
+    build-depends: directory >= 1.3.1    && < 1.4
+    build-depends: filepath  >= 1.4.1.0  && < 1.6
+    build-depends: time      >= 1.6.0.1  && < 2
 
     other-modules:
       System.PosixCompat.Internal.Time
 
   else
-    build-depends: unix >= 2.4 && < 2.9
-    include-dirs: include
-    includes: HsUnixCompat.h
-    install-includes: HsUnixCompat.h
-    c-sources: cbits/HsUnixCompat.c
+    build-depends: unix      >= 2.7.2.0  && < 2.9
+    if arch(wasm32)
+      c-sources: cbits/mktemp.c
+    else
+      include-dirs: include
+      includes: HsUnixCompat.h
+      install-includes: HsUnixCompat.h
+      c-sources: cbits/HsUnixCompat.c
     if os(solaris)
       cc-options: -DSOLARIS
+
+  default-language: Haskell2010
+  ghc-options:
+    -Wall
+    -Wcompat
+
+Test-Suite unix-compat-testsuite
+  type: exitcode-stdio-1.0
+  hs-source-dirs: tests
+  main-is: main.hs
+
+  other-modules:
+     MkstempSpec
+     LinksSpec
+     ProcessSpec
+
+  build-depends:
+      unix-compat
+    , base
+    , monad-parallel
+    , hspec >= 2.5.5
+    , HUnit
+    , directory >= 1.3.1.0
+        -- directory-1.3.1.0 adds createFileLink
+    , temporary
+
+  default-language: Haskell2010
+  ghc-options:
+    -Wall
+    -Wcompat
