diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,11 @@
+haskell-libmagic (1.2) unstable; urgency=medium
+
+  * Initial release, forked from John Goerzen's magic package.
+  * Don't use unsafe FFI calls for blocking I/O
+    (thanks, Niklas Hambüchen)
+  * Made the Magic handle thread safe.
+  * magicLoad takes a list of paths.
+  * Convert from FilePath to OsPath.
+  * Remove magicCompile.
+
+ -- Joey Hess <id@joeyh.name>  Mon, 26 Oct 2026 10:02:59 -0400
diff --git a/COPYING b/COPYING
new file mode 100644
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,26 @@
+Copyright (c) 2005 John Goerzen.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. 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.
+3. Neither the name of John Goerzen nor the names of contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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/Magic.hs b/Magic.hs
new file mode 100644
--- /dev/null
+++ b/Magic.hs
@@ -0,0 +1,33 @@
+{- -*- Mode: haskell; -*-
+Haskell Magic Interface
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+{- |
+   Module     : Magic
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Top-level Magic module.
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+module Magic (-- * Basic Types
+             module Magic.Types,
+             -- * Initialization
+             module Magic.Init,
+             -- * Operation
+             module Magic.Operations
+            )
+where
+import Magic.Types
+import Magic.Init
+import Magic.Operations
diff --git a/Magic/Data.hsc b/Magic/Data.hsc
new file mode 100644
--- /dev/null
+++ b/Magic/Data.hsc
@@ -0,0 +1,77 @@
+-- AUTO-GENERATED FILE, DO NOT EDIT.  GENERATED BY utils/genconsts.hs
+{- |
+   Module     : Magic.Data
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Haskell types for libmagic constants
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+module Magic.Data (module Magic.Data) where
+
+#include "magic.h"
+
+
+data MagicFlag =
+   MagicNone
+ | MagicDebug
+ | MagicSymlink
+ | MagicCompress
+ | MagicDevices
+ | MagicMimeType
+ | MagicMimeEncoding
+ | MagicMime
+ | MagicContinue
+ | MagicCheck
+ | MagicPreserveAtime
+ | MagicRaw
+ | MagicError
+ | UnknownMagicFlag Int
+
+ deriving (Show)
+
+instance Enum MagicFlag where
+ toEnum (#{const MAGIC_NONE}) = MagicNone
+ toEnum (#{const MAGIC_DEBUG}) = MagicDebug
+ toEnum (#{const MAGIC_SYMLINK}) = MagicSymlink
+ toEnum (#{const MAGIC_COMPRESS}) = MagicCompress
+ toEnum (#{const MAGIC_DEVICES}) = MagicDevices
+ toEnum (#{const MAGIC_MIME_TYPE}) = MagicMimeType
+ toEnum (#{const MAGIC_MIME_ENCODING}) = MagicMimeEncoding
+ toEnum (#{const MAGIC_MIME}) = MagicMime
+ toEnum (#{const MAGIC_CONTINUE}) = MagicContinue
+ toEnum (#{const MAGIC_CHECK}) = MagicCheck
+ toEnum (#{const MAGIC_PRESERVE_ATIME}) = MagicPreserveAtime
+ toEnum (#{const MAGIC_RAW}) = MagicRaw
+ toEnum (#{const MAGIC_ERROR}) = MagicError
+ toEnum x = UnknownMagicFlag x
+
+ fromEnum MagicNone = (#{const MAGIC_NONE})
+ fromEnum MagicDebug = (#{const MAGIC_DEBUG})
+ fromEnum MagicSymlink = (#{const MAGIC_SYMLINK})
+ fromEnum MagicCompress = (#{const MAGIC_COMPRESS})
+ fromEnum MagicDevices = (#{const MAGIC_DEVICES})
+ fromEnum MagicMimeType = (#{const MAGIC_MIME_TYPE})
+ fromEnum MagicMimeEncoding = (#{const MAGIC_MIME_ENCODING})
+ fromEnum MagicMime = (#{const MAGIC_MIME})
+ fromEnum MagicContinue = (#{const MAGIC_CONTINUE})
+ fromEnum MagicCheck = (#{const MAGIC_CHECK})
+ fromEnum MagicPreserveAtime = (#{const MAGIC_PRESERVE_ATIME})
+ fromEnum MagicRaw = (#{const MAGIC_RAW})
+ fromEnum MagicError = (#{const MAGIC_ERROR})
+ fromEnum (UnknownMagicFlag x) = x
+
+instance Ord MagicFlag where
+ compare x y = compare (fromEnum x) (fromEnum y)
+
+instance Eq MagicFlag where
+ x == y = (fromEnum x) == (fromEnum y)
+
+
diff --git a/Magic/Init.hsc b/Magic/Init.hsc
new file mode 100644
--- /dev/null
+++ b/Magic/Init.hsc
@@ -0,0 +1,69 @@
+{- -*- Mode: haskell; -*-
+Haskell magic Interface
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+{- |
+   Module     : Magic.Init
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Initialization and shutdown for magic programs
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Magic.Init(magicOpen, magicLoad, magicLoadDefault)
+where
+
+import Foreign.Ptr
+import Foreign.C.String
+import Magic.Types
+import Foreign.C.Types
+import Magic.Utils
+import Magic.TypesLL
+import qualified Data.List.NonEmpty as NE
+import System.OsPath.Types
+import System.OsString.Internal.Types
+import Data.Coerce
+import qualified Data.ByteString.Short as SBS
+
+{- | Create a Magic object.  You must call either 'magicLoadDefault'
+or 'magicLoad' after this.
+-}
+magicOpen :: [MagicFlag] -> IO Magic
+magicOpen mfl =
+    fromMagicPtr "magicOpen" (magic_open flags)
+    where flags = flaglist2int mfl
+
+{- | Load the system's default magic database. -}
+magicLoadDefault :: Magic -> IO ()
+magicLoadDefault m = withMagicPtr m $ \cmagic ->
+    checkIntError "magicLoadDefault" cmagic $ magic_load cmagic nullPtr
+
+{- | Load the specified magic databases. -}
+magicLoad :: Magic -> NE.NonEmpty OsPath -> IO ()
+magicLoad m l = withMagicPtr m $ \cmagic ->
+    SBS.useAsCString s $ \cs ->
+     checkIntError "magicLoad" cmagic $ magic_load cmagic cs
+  where
+    s = SBS.intercalate ":" (map conv (NE.toList l))
+
+    conv p = case coercionToPlatformTypes of
+        Right _ -> getWindowsString (coerce p)
+        Left _ -> getPosixString (coerce p)
+
+foreign import ccall unsafe "magic.h magic_open"
+  magic_open :: CInt -> IO (Ptr CMagic)
+
+foreign import ccall safe "magic.h magic_load"
+  magic_load :: Ptr CMagic -> CString -> IO CInt
diff --git a/Magic/Operations.hsc b/Magic/Operations.hsc
new file mode 100644
--- /dev/null
+++ b/Magic/Operations.hsc
@@ -0,0 +1,90 @@
+{- -*- Mode: haskell; -*-
+Haskell magic Interface
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+{- |
+   Module     : Magic.Init
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Initialization and shutdown for magic programs
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+module Magic.Operations(-- * Guessing the type
+                        magicFile, magicStdin,
+                        magicString, magicCString,
+                        -- * Other operations
+                        magicSetFlags)
+where
+
+import Foreign.Ptr
+import Foreign.C.String
+import Magic.Types
+import Foreign.C.Types
+import Data.Word
+import Magic.Utils
+import Magic.TypesLL
+import System.OsPath.Types
+import System.OsString.Internal.Types
+import Data.Coerce
+import qualified Data.ByteString.Short as SBS
+
+{- | Calls the Magic system on the specified file. -}
+magicFile :: Magic -> OsPath -> IO String
+magicFile magic fp =
+    withMagicPtr magic $ \cmagic ->
+      SBS.useAsCString sbsfp $ \cfp -> do
+        res <- throwErrorIfNull "magicFile" cmagic (magic_file cmagic cfp)
+        peekCString res
+  where
+      sbsfp = case coercionToPlatformTypes of
+        Right _ -> getWindowsString (coerce fp)
+        Left _ -> getPosixString (coerce fp)
+
+{- | Calls the Magic system on stdin. -}
+magicStdin :: Magic -> IO String
+magicStdin magic =
+    withMagicPtr magic $ \cmagic ->
+     do res <- throwErrorIfNull "magicStdin" cmagic (magic_file cmagic nullPtr)
+        peekCString res
+
+{- | Calls the Magic system to process the given String.  Please note:
+it is not evaluated lazily. -}
+magicString :: Magic -> String -> IO String
+magicString m s = withCStringLen s (magicCString m)
+
+{- | Lower-level function used to call the Magic system to process a C 
+string. -}
+magicCString :: Magic -> CStringLen -> IO String
+magicCString magic (cstr, len) =
+    withMagicPtr magic $ \cmagic ->
+     do res <- throwErrorIfNull "magicCString" cmagic (magic_buffer cmagic cstr (fromIntegral len))
+        peekCString res
+
+{- | Change the flags on an already-created object. -}
+magicSetFlags :: Magic -> [MagicFlag] -> IO ()
+magicSetFlags m mfl = withMagicPtr m $ \cmagic ->
+     checkIntError "magicSetFlags" cmagic $ magic_setflags cmagic flags
+    where flags = flaglist2int mfl
+
+-- Does file I/O -> safe
+foreign import ccall safe "magic.h magic_file"
+  magic_file :: Ptr CMagic -> CString -> IO CString
+
+-- Does not do I/O -> unsafe
+foreign import ccall unsafe "magic.h magic_buffer"
+  magic_buffer :: Ptr CMagic -> CString -> #{type size_t} -> IO CString
+
+-- Does not do I/O -> unsafe
+foreign import ccall unsafe "magic.h magic_setflags"
+  magic_setflags :: Ptr CMagic -> CInt -> IO CInt
diff --git a/Magic/Types.hs b/Magic/Types.hs
new file mode 100644
--- /dev/null
+++ b/Magic/Types.hs
@@ -0,0 +1,28 @@
+{-  -*- Mode: haskell; -*-
+Haskell magic Interface
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+{- |
+   Module     : Magic.Types
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Types for magic programs.
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+module Magic.Types(Magic,
+                   MagicFlag(..))
+where
+
+import Magic.TypesLL
+import Magic.Data
diff --git a/Magic/TypesLL.hsc b/Magic/TypesLL.hsc
new file mode 100644
--- /dev/null
+++ b/Magic/TypesLL.hsc
@@ -0,0 +1,41 @@
+{-  -*- Mode: haskell; -*-
+Haskell magic Interface
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+{- |
+   Module     : Magic.TypesLL
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Low-Types for magic programs.
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+module Magic.TypesLL(CMagic, Magic(..))
+where
+
+import Foreign.ForeignPtr
+import Control.Concurrent.MVar
+
+#include <magic.h>
+
+data CMagic
+
+{- | Main Magic handle.
+
+Magic objects are automatically closed (and memory freed) when they are
+garbage-collected by Haskell.  There is no need to explicitly close them.
+
+Access to the C library is serialized, so multiple threads may safely
+use the same Magic handle.
+-}
+newtype Magic = Magic (MVar (ForeignPtr CMagic))
diff --git a/Magic/Utils.hsc b/Magic/Utils.hsc
new file mode 100644
--- /dev/null
+++ b/Magic/Utils.hsc
@@ -0,0 +1,76 @@
+{- -*- Mode: haskell; -*-
+Haskell magic Interface
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+{- |
+   Module     : Magic.Utils
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD
+
+   Maintainer : Joey Hess,
+   Maintainer : id\@joeyh.name
+   Stability  : provisional
+   Portability: portable
+
+Utils
+
+Written by John Goerzen, jgoerzen\@complete.org
+-}
+
+module Magic.Utils (flaglist2int, fromMagicPtr, withMagicPtr, checkIntError,
+                    throwErrorIfNull)
+where
+
+import Foreign
+import Foreign.C.Error
+import Foreign.C.String
+import Magic.TypesLL
+import Magic.Types
+import Foreign.C.Types
+import Control.Concurrent.MVar
+
+flaglist2int :: [MagicFlag] -> CInt
+flaglist2int mfl =
+    foldl (\c f -> c .|. (fromIntegral . fromEnum $ f)) 0 mfl
+
+fromMagicPtr :: String -> IO (Ptr CMagic) -> IO Magic
+fromMagicPtr caller action =
+    do ptr <- throwErrnoIfNull caller action
+       m <- newForeignPtr magic_close ptr
+       mvar <- newMVar m
+       return (Magic mvar)
+
+throwErrorIfNull :: String -> Ptr CMagic -> IO (Ptr a) -> IO (Ptr a)
+throwErrorIfNull caller m action =
+    do res <- action
+       if res == nullPtr
+          then throwError caller m
+          else return res
+
+withMagicPtr :: Magic -> (Ptr CMagic -> IO a) -> IO a
+withMagicPtr (Magic mvar) a = withMVar mvar $ \m -> withForeignPtr m a
+
+throwError :: String -> Ptr CMagic -> IO a
+throwError caller cmagic =
+               do errormsg <- magic_error cmagic
+                  if errormsg /= nullPtr
+                     then do em <- peekCString errormsg
+                             fail $ caller ++ ": " ++ em
+                     else fail $ caller ++ ": got error code but no error message"
+
+checkIntError :: String -> Ptr CMagic -> IO CInt -> IO ()
+checkIntError caller cmagic action = 
+    do res <- action
+       if res == 0
+          then return ()
+          else throwError caller cmagic
+
+
+foreign import ccall unsafe "magic.h &magic_close"
+  magic_close :: FunPtr (Ptr CMagic -> IO ())
+
+foreign import ccall unsafe "magic.h magic_error"
+  magic_error :: Ptr CMagic -> IO CString
diff --git a/libmagic.cabal b/libmagic.cabal
new file mode 100644
--- /dev/null
+++ b/libmagic.cabal
@@ -0,0 +1,42 @@
+Name: libmagic
+Version: 1.2
+Cabal-Version: >= 1.10
+License: BSD3
+Maintainer: Joey Hess <id@joeyh.name>
+Author: John Goerzen
+Stability: Stable
+Copyright: Copyright (c) 2005-2009 John Goerzen
+Build-Type: Simple
+license-file: COPYING
+Category: Text
+Synopsis: Interface to C file/magic library
+Description:  This package provides a Haskell interface to the C libmagic library.
+ With it, you can determine the type of a file by examining its contents
+ rather than its name.  The Haskell interface provides a full-featured
+ binding.
+Extra-Source-Files: CHANGELOG
+
+Library
+  Extra-Libraries: magic
+  Exposed-Modules:
+    Magic,
+    Magic.Data,
+    Magic.Types,
+    Magic.Init,
+    Magic.Operations
+  Other-Modules:
+    Magic.Utils,
+    Magic.TypesLL
+  Build-Depends:
+    base >= 3 && < 5,
+    filepath >= 1.4.100 && < 1.6,
+    os-string >= 2.0.0 && < 2.1,
+    bytestring < 0.13
+  GHC-Options: -O2 -Wall
+  Default-Language: Haskell2010
+  Default-Extensions: ForeignFunctionInterface, TypeSynonymInstances,
+              EmptyDataDecls
+
+source-repository head
+  type: git
+  location: https://git.joeyh.name/git/haskell-libmagic.git
