diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,20 @@
+# Changelog for `magic`
+
+All notable changes to this project are documented here.
+
+## 1.1.1 (2026-06-09)
+
+No API changes: this release is a drop-in replacement for 1.1.
+
+- New maintainer: Philippe (<philippedev101@gmail.com>); package handed over from John Goerzen.
+- Modernised the Cabal package description (`cabal-version: 2.4`, proper `library`/`test-suite` stanzas, `source-repository`, `tested-with`).
+- Added an automatic `pkgconfig` flag: libmagic is now located via pkg-config when available, falling back transparently to plain `extra-libraries` linking otherwise.
+- Moved library sources under `src/` and tests under `test/` (no code or module-name changes).
+- Revived the original HUnit test suite in place: completed the unfinished `Inittest` module with real tests exercising the bindings against the system magic database, and fixed the runner to exit non-zero on failure.
+- Added a GitHub Actions CI workflow building and testing across GHC 9.6 / 9.8 / 9.10 / 9.12.
+- Removed dead imports so the library builds `-Wall`-clean.
+- Ships the `unsafe`-FFI fix (use safe FFI calls for blocking I/O) that was merged upstream after the 1.1 Hackage release but never published.
+
+## 1.1 (2014-10) and earlier
+
+Releases by John Goerzen. See the git history for details.
diff --git a/Magic.hs b/Magic.hs
deleted file mode 100644
--- a/Magic.hs
+++ /dev/null
@@ -1,35 +0,0 @@
-{- -*- 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 : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   Stability  : provisional
-   Portability: portable
-
-Top-level Magic module.
-
-Written by John Goerzen, jgoerzen\@complete.org
-
-Foo bar
--}
-
-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
deleted file mode 100644
--- a/Magic/Data.hsc
+++ /dev/null
@@ -1,77 +0,0 @@
--- AUTO-GENERATED FILE, DO NOT EDIT.  GENERATED BY utils/genconsts.hs
-{- |
-   Module     : Magic.Data
-   Copyright  : Copyright (C) 2005 John Goerzen
-   License    : BSD
-
-   Maintainer : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   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
deleted file mode 100644
--- a/Magic/Init.hsc
+++ /dev/null
@@ -1,58 +0,0 @@
-{- -*- 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 : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   Stability  : provisional
-   Portability: portable
-
-Initialization and shutdown for magic programs
-
-Written by John Goerzen, jgoerzen\@complete.org
--}
-
-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 Foreign.Marshal.Utils
-
-{- | 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" m $ magic_load cmagic nullPtr)
-
-{- | Load the specified magic database(s).  The given string may contain
-multiple colon-separated pathnames. -}
-magicLoad :: Magic -> String -> IO ()
-magicLoad m s = withMagicPtr m (\cmagic ->
-    withCString s (\cs ->
-     checkIntError "magicLoad" m $ magic_load cmagic cs))
-    
-foreign import ccall unsafe "magic.h magic_open"
-  magic_open :: CInt -> IO (Ptr CMagic)
-
-foreign import ccall unsafe "magic.h magic_load"
-  magic_load :: Ptr CMagic -> CString -> IO CInt
diff --git a/Magic/Operations.hsc b/Magic/Operations.hsc
deleted file mode 100644
--- a/Magic/Operations.hsc
+++ /dev/null
@@ -1,102 +0,0 @@
-{- -*- 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 : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   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, magicCompile)
-where
-
-import Foreign.Ptr
-import Foreign.C.String
-import Magic.Types
-import Foreign.C.Types
-import Data.Word
-import Foreign.C.String
-import Foreign.C.Error
-import Magic.Utils
-import Magic.TypesLL
-import Foreign.Marshal.Utils
-
-{- | Calls the Magic system on the specified file. -}
-magicFile :: Magic -> FilePath -> IO String
-magicFile magic fp =
-    withMagicPtr magic (\cmagic ->
-    withCString fp (\cfp ->
-     do res <- throwErrorIfNull "magicFile" magic (magic_file cmagic cfp)
-        peekCString res
-                    )
-                       )
-
-{- | Calls the Magic system on stdin. -}
-magicStdin :: Magic -> IO String
-magicStdin magic =
-    withMagicPtr magic (\cmagic ->
-     do res <- throwErrorIfNull "magicStdin" magic (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" magic (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" m $ magic_setflags cmagic flags)
-    where flags = flaglist2int mfl
-
-{- | Compile the colon-separated list of database file(s).  The compiled files
-created have .mgc added to the names of the argument.
--}
-magicCompile :: Magic           -- ^ Object to use
-             -> Maybe String    -- ^ Colon separated list of databases, or Nothing for default
-             -> IO ()
-magicCompile m mstr = withMagicPtr m (\cm ->
-     case mstr of
-               Nothing -> worker cm nullPtr
-               Just x -> withCString x (worker cm)
-                                     )
-    where worker cm cs = checkIntError "magicCompile" m $ magic_compile cm cs
-
-foreign import ccall unsafe "magic.h magic_file"
-  magic_file :: Ptr CMagic -> CString -> IO CString
-
-foreign import ccall unsafe "magic.h magic_buffer"
-  magic_buffer :: Ptr CMagic -> CString -> #{type size_t} -> IO CString
-
-foreign import ccall unsafe "magic.h magic_setflags"
-  magic_setflags :: Ptr CMagic -> CInt -> IO CInt
-
-foreign import ccall unsafe "magic.h magic_compile"
-  magic_compile :: Ptr CMagic -> CString -> IO CInt
diff --git a/Magic/Types.hsc b/Magic/Types.hsc
deleted file mode 100644
--- a/Magic/Types.hsc
+++ /dev/null
@@ -1,42 +0,0 @@
-{-  -*- 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 : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   Stability  : provisional
-   Portability: portable
-
-Types for magic programs.
-
-Written by John Goerzen, jgoerzen\@complete.org
--}
-
-module Magic.Types(Magic,
-                   MagicFlag(..))
-where
-import Foreign.Ptr
-import Data.Word
-import Data.Int
-import Foreign.C.Types
-import Foreign.ForeignPtr
-import Magic.Data
-import Magic.TypesLL
-
-#include <magic.h>
-
-{- | Main Magic object type.
-
-Magic objects are automatically closed (and memory freed) when they are
-garbage-collected by Haskell.  There is no need to explicitly close them.
--}
-type Magic = ForeignPtr CMagic
-
diff --git a/Magic/TypesLL.hs b/Magic/TypesLL.hs
deleted file mode 100644
--- a/Magic/TypesLL.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-{-  -*- 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 : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   Stability  : provisional
-   Portability: portable
-
-Low-Types for magic programs.
-
-Written by John Goerzen, jgoerzen\@complete.org
--}
-
-module Magic.TypesLL(CMagic)
-where
-
-data CMagic
diff --git a/Magic/Utils.hsc b/Magic/Utils.hsc
deleted file mode 100644
--- a/Magic/Utils.hsc
+++ /dev/null
@@ -1,77 +0,0 @@
-{- -*- 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 : John Goerzen,
-   Maintainer : jgoerzen\@complete.org
-   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 Foreign.ForeignPtr
-import Magic.TypesLL
-import Magic.Types
-import Data.Bits
-import Foreign.C.Types
-import Magic.Data
-
-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
-       newForeignPtr magic_close ptr
-
-throwErrorIfNull :: String -> Magic -> 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 m = withForeignPtr m
-
-throwError :: String -> Magic -> IO a
-throwError caller m = withMagicPtr m (\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 -> Magic -> IO CInt -> IO ()
-checkIntError caller m action = 
-    do res <- action
-       if res == 0
-          then return ()
-          else throwError caller m
-
-
-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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+# magic
+
+It is a binding to the C [libmagic](https://www.darwinsys.com/file/) library. It allows you to determine the type of a file not by looking at its name or extension, but rather by examining the contents itself.
+
+libmagic can provide either a textual description or a MIME content type (and, occasionally, also a character set). The Haskell binding can also provide reports over Haskell strings.
+
+## Requirements
+
+You need the C `libmagic` library and its development headers installed before building. It is part of the [`file`](https://www.darwinsys.com/file/) package.
+
+| Platform | Install |
+|----------|---------|
+| Debian / Ubuntu | `apt-get install libmagic-dev` |
+| Fedora | `dnf install file-devel` |
+| Arch | `pacman -S file` |
+| macOS (Homebrew) | `brew install libmagic` |
+| Nix | `file` / `file.dev` |
+
+## Building
+
+With `cabal`:
+
+```sh
+cabal build
+cabal test
+```
+
+With `stack`:
+
+```sh
+stack build
+stack test
+```
+
+libmagic is located automatically via pkg-config. If pkg-config cannot find it (for example, an unusual install location), point Cabal at the library directly:
+
+```sh
+cabal build \
+  --extra-lib-dirs=/path/to/lib \
+  --extra-include-dirs=/path/to/include
+```
+
+## Usage
+
+You can simply add `magic` to your `build-depends` to enable this library.
+
+```haskell
+import Magic
+
+main :: IO ()
+main = do
+  magic <- magicOpen [MagicMimeType]
+  magicLoadDefault magic
+  mime <- magicFile magic "some-file.png"
+  putStrLn mime          -- e.g. "image/png"
+```
+
+## Author & history
+
+magic-haskell was written by John Goerzen <jgoerzen@complete.org>, who created it in 2005 and maintained it for nearly two decades. It is now maintained by Philippe, with development at <https://github.com/philippedev101/magic-haskell>.
+
+## License
+
+3-clause BSD. See the `COPYING` file included with the package.
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/Setup.lhs b/Setup.lhs
deleted file mode 100644
--- a/Setup.lhs
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env runhugs
-arch-tag: Main setup script
-
-> import Distribution.Simple
-
-> main = defaultMain
-
diff --git a/magic.cabal b/magic.cabal
--- a/magic.cabal
+++ b/magic.cabal
@@ -1,31 +1,118 @@
-Extra-Libraries: magic
--- End detected settings section.  Everything below here should not
--- need editing.
-Name: magic
-Version: 1.1
-license: BSD3
-Maintainer: John Goerzen <jgoerzen@complete.org>
-Author: John Goerzen
-Stability: Alpha
-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.
--- C-Sources: glue/glue.c
-Exposed-Modules: Magic,
- Magic.Data,
- Magic.Types,
- Magic.Init,
- Magic.Operations
-Other-Modules: Magic.Utils,
- Magic.TypesLL
-Build-Depends: base >= 3 && < 5
-GHC-Options: -O2
--- CC-Options: -Iglue
-Extensions: ForeignFunctionInterface, TypeSynonymInstances,
-            EmptyDataDecls
+cabal-version:      2.4
+name:               magic
+version:            1.1.1
+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.
+  .
+  Note: this package requires the C libmagic library (part of the @file@
+  package) and its development headers to be installed at build time.
+
+category:           Text
+license:            BSD-3-Clause
+license-file:       COPYING
+author:             John Goerzen
+maintainer:         Philippe <philippedev101@gmail.com>
+copyright:          Copyright (c) 2005-2009 John Goerzen
+homepage:           https://github.com/philippedev101/magic-haskell
+bug-reports:        https://github.com/philippedev101/magic-haskell/issues
+build-type:         Simple
+extra-doc-files:
+  README.md
+  CHANGELOG.md
+
+tested-with:
+  GHC ==9.6.7
+   || ==9.8.4
+   || ==9.10.3
+   || ==9.12.4
+
+source-repository head
+  type:     git
+  location: https://github.com/philippedev101/magic-haskell.git
+
+flag pkgconfig
+  description:
+    Use pkg-config to locate the C libmagic library. Enabled by default;
+    because this flag is automatic, Cabal's solver transparently falls back
+    to plain @extra-libraries@ linking when pkg-config or libmagic.pc is
+    unavailable.
+  default: True
+  manual:  False
+
+flag dev
+  description: Turn on developer settings, notably -Werror.
+  default:     False
+  manual:      True
+
+-- Strict warning set shared by all components. Based on -Weverything with the
+-- pedantic / Safe-Haskell / style categories that the existing bindings cannot
+-- satisfy without source changes disabled. The genuine bug-catching warnings
+-- (unused bindings, incomplete patterns, name shadowing, missing local/export
+-- signatures, type defaults, ...) remain ON. -Werror is opt-in via the `dev`
+-- flag so a normal build, Hackage, or Stackage is never broken by a future
+-- GHC introducing new warnings.
+common warnings
+  ghc-options:
+    -Weverything
+    -Wno-all-missed-specialisations
+    -Wno-missed-specialisations
+    -Wno-implicit-prelude
+    -Wno-missing-deriving-strategies
+    -Wno-missing-import-lists
+    -Wno-missing-kind-signatures
+    -Wno-missing-safe-haskell-mode
+    -Wno-prepositive-qualified-module
+    -Wno-safe
+    -Wno-unsafe
+
+  if flag(dev)
+    ghc-options: -Werror
+
+library
+  import:           warnings
+  hs-source-dirs:   src
+  default-language: Haskell2010
+  exposed-modules:
+    Magic
+    Magic.Data
+    Magic.Types
+    Magic.Init
+    Magic.Operations
+
+  other-modules:
+    Magic.Utils
+    Magic.TypesLL
+
+  default-extensions:
+    EmptyDataDecls
+    ForeignFunctionInterface
+    TypeSynonymInstances
+
+  build-depends:    base >=4.9 && <5
+
+  if flag(pkgconfig)
+    pkgconfig-depends: libmagic
+  else
+    extra-libraries:   magic
+
+test-suite magic-test
+  import:           warnings
+  type:             exitcode-stdio-1.0
+  main-is:          runtests.hs
+  hs-source-dirs:   test
+  other-modules:
+    Inittest
+    Tests
+
+  default-language: Haskell2010
+  build-depends:
+    , base        >=4.9  && <5
+    , bytestring  >=0.10 && <0.13
+    , directory   >=1.2  && <1.4
+    , filepath    >=1.4  && <1.6
+    , HUnit       >=1.6  && <1.7
+    , magic
diff --git a/src/Magic.hs b/src/Magic.hs
new file mode 100644
--- /dev/null
+++ b/src/Magic.hs
@@ -0,0 +1,55 @@
+{- -*- 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-3-Clause
+
+   Maintainer : Philippe <philippedev101\@gmail.com>
+   Stability  : provisional
+   Portability: portable
+
+Haskell bindings to the C @libmagic@ library, which identifies the type of a
+file by inspecting its contents rather than its name. It can report a textual
+description, a MIME type, or a character-set encoding.
+
+This top-level module re-exports the whole interface: the types in
+"Magic.Types", the initialization functions in "Magic.Init", and the querying
+functions in "Magic.Operations".
+
+A typical session creates a handle, loads the system magic database, then
+queries files or in-memory data. The flags passed to 'magicOpen' (see
+'MagicFlag') choose what kind of answer comes back:
+
+> import Magic
+>
+> main :: IO ()
+> main = do
+>   magic <- magicOpen [MagicMimeType]   -- ask for MIME types
+>   magicLoadDefault magic               -- load the system database
+>   mime <- magicFile magic "/etc/passwd"
+>   putStrLn mime                        -- e.g. "text/plain"
+
+Handles are closed and their memory freed automatically when they are
+garbage-collected (see 'Magic'); there is no explicit close. On failure the
+operations in this library raise an 'IOError'.
+
+Originally written by John Goerzen.
+-}
+
+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/src/Magic/Data.hsc b/src/Magic/Data.hsc
new file mode 100644
--- /dev/null
+++ b/src/Magic/Data.hsc
@@ -0,0 +1,92 @@
+-- AUTO-GENERATED FILE, DO NOT EDIT.  GENERATED BY utils/genconsts.hs
+{- |
+   Module     : Magic.Data
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD-3-Clause
+
+   Maintainer : Philippe <philippedev101\@gmail.com>
+   Stability  : provisional
+   Portability: portable
+
+The 'MagicFlag' enumeration, mapping the C @libmagic@ @MAGIC_*@ constants
+to Haskell.
+-}
+
+module Magic.Data (module Magic.Data) where
+
+#include "magic.h"
+
+
+-- | Flags that control how @libmagic@ examines a file and what it
+-- reports. Combine them in the list passed to @magicOpen@ or
+-- @magicSetFlags@.
+data MagicFlag
+    = -- | No special handling; return a textual description (the default).
+      MagicNone
+    | -- | Print debugging messages to stderr.
+      MagicDebug
+    | -- | Follow symbolic links.
+      MagicSymlink
+    | -- | Look inside compressed files.
+      MagicCompress
+    | -- | Look at the contents of block or character special devices.
+      MagicDevices
+    | -- | Return a MIME type string instead of a textual description.
+      MagicMimeType
+    | -- | Return the MIME encoding (character set) instead of a textual description.
+      MagicMimeEncoding
+    | -- | Return both the MIME type and the encoding.
+      MagicMime
+    | -- | Return all matches, not just the first.
+      MagicContinue
+    | -- | Check the magic database for consistency and report problems.
+      MagicCheck
+    | -- | Preserve the access time of examined files.
+      MagicPreserveAtime
+    | -- | Do not translate unprintable characters to octal escapes.
+      MagicRaw
+    | -- | Treat errors while examining a file as real errors instead of embedding them in the result.
+      MagicError
+    | -- | A flag value returned by libmagic that these bindings do not
+      --   recognise, carrying its raw integer value.
+      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/src/Magic/Init.hsc b/src/Magic/Init.hsc
new file mode 100644
--- /dev/null
+++ b/src/Magic/Init.hsc
@@ -0,0 +1,64 @@
+{- -*- 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-3-Clause
+
+   Maintainer : Philippe <philippedev101\@gmail.com>
+   Stability  : provisional
+   Portability: portable
+
+Creating a magic handle and loading magic databases into it. A handle returned
+by 'magicOpen' must be given a database with 'magicLoadDefault' or 'magicLoad'
+before it can be queried with the functions in "Magic.Operations".
+
+Written by John Goerzen.
+-}
+
+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
+
+{- | Create a new magic handle configured with the given flags (see
+'MagicFlag'). Before querying anything you must load a database with
+'magicLoadDefault' or 'magicLoad'.
+
+The handle is freed automatically when it is garbage-collected. Raises an
+'IOError' if the handle cannot be created.
+-}
+magicOpen :: [MagicFlag] -> IO Magic
+magicOpen mfl =
+    fromMagicPtr "magicOpen" (magic_open flags)
+    where flags = flaglist2int mfl
+
+{- | Load the system's default magic database into the handle. Raises an
+'IOError' if the database cannot be loaded. -}
+magicLoadDefault :: Magic -> IO ()
+magicLoadDefault m = withMagicPtr m (\cmagic ->
+    checkIntError "magicLoadDefault" m $ magic_load cmagic nullPtr)
+
+{- | Load the given magic database(s) into the handle. The argument may be a
+single path or several colon-separated paths. Raises an 'IOError' if a database
+cannot be loaded. -}
+magicLoad :: Magic -> String -> IO ()
+magicLoad m s = withMagicPtr m (\cmagic ->
+    withCString s (\cs ->
+     checkIntError "magicLoad" m $ magic_load cmagic cs))
+    
+foreign import ccall unsafe "magic.h magic_open"
+  magic_open :: CInt -> IO (Ptr CMagic)
+
+foreign import ccall unsafe "magic.h magic_load"
+  magic_load :: Ptr CMagic -> CString -> IO CInt
diff --git a/src/Magic/Operations.hsc b/src/Magic/Operations.hsc
new file mode 100644
--- /dev/null
+++ b/src/Magic/Operations.hsc
@@ -0,0 +1,117 @@
+{- -*- 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.Operations
+   Copyright  : Copyright (C) 2005 John Goerzen
+   License    : BSD-3-Clause
+
+   Maintainer : Philippe <philippedev101\@gmail.com>
+   Stability  : provisional
+   Portability: portable
+
+Querying the type of files and in-memory data, and other operations on a magic
+handle. The handle must first be created with @magicOpen@ and populated with
+@magicLoadDefault@ or @magicLoad@ (see "Magic.Init").
+
+Written by John Goerzen.
+-}
+
+module Magic.Operations(-- * Guessing the type
+                        magicFile, magicStdin,
+                        magicString, magicCString,
+                        -- * Other operations
+                        magicSetFlags, magicCompile)
+where
+
+import Foreign.Ptr
+import Foreign.C.String
+import Magic.Types
+import Foreign.C.Types
+import Data.Word
+import Magic.Utils
+import Magic.TypesLL
+
+{- | Identify the file at the given path. The result is in the form selected by
+the handle's flags (a textual description, a MIME type, an encoding, and so on;
+see 'MagicFlag'). Raises an 'IOError' if the file cannot be examined. -}
+magicFile :: Magic -> FilePath -> IO String
+magicFile magic fp =
+    withMagicPtr magic (\cmagic ->
+    withCString fp (\cfp ->
+     do res <- throwErrorIfNull "magicFile" magic (magic_file cmagic cfp)
+        peekCString res
+                    )
+                       )
+
+{- | Identify the data available on standard input, as 'magicFile' does for a
+named file. Raises an 'IOError' if the data cannot be examined. -}
+magicStdin :: Magic -> IO String
+magicStdin magic =
+    withMagicPtr magic (\cmagic ->
+     do res <- throwErrorIfNull "magicStdin" magic (magic_file cmagic nullPtr)
+        peekCString res
+                       )
+
+{- | Identify the contents of the given 'String'. Note that the string is
+processed strictly, not lazily.
+
+This is convenient for textual data. For binary data prefer 'magicCString' (or
+write it to a file and use 'magicFile'): marshalling through 'String' goes via
+the current locale encoding, which can corrupt non-textual bytes. Raises an
+'IOError' if the data cannot be examined. -}
+magicString :: Magic -> String -> IO String
+magicString m s = withCStringLen s (magicCString m)
+
+{- | Identify the contents of a C string buffer (a pointer and a length). This
+is the lower-level primitive behind 'magicString', and the right choice for raw
+binary data since it does no encoding conversion. Raises an 'IOError' if the
+data cannot be examined. -}
+magicCString :: Magic -> CStringLen -> IO String
+magicCString magic (cstr, len) =
+    withMagicPtr magic (\cmagic ->
+     do res <- throwErrorIfNull "magicCString" magic (magic_buffer cmagic cstr (fromIntegral len))
+        peekCString res
+                    )
+
+{- | Change the flags (see 'MagicFlag') on an existing handle, for example to
+switch between textual descriptions and MIME types. Raises an 'IOError' on
+failure. -}
+magicSetFlags :: Magic -> [MagicFlag] -> IO ()
+magicSetFlags m mfl = withMagicPtr m (\cmagic ->
+     checkIntError "magicSetFlags" m $ magic_setflags cmagic flags)
+    where flags = flaglist2int mfl
+
+{- | Compile the given colon-separated magic database file(s) into the binary
+@.mgc@ form. Each compiled file is named after its source with @.mgc@ appended.
+Pass 'Nothing' to compile the default database. Raises an 'IOError' on failure.
+-}
+magicCompile :: Magic           -- ^ Object to use
+             -> Maybe String    -- ^ Colon separated list of databases, or Nothing for default
+             -> IO ()
+magicCompile m mstr = withMagicPtr m (\cm ->
+     case mstr of
+               Nothing -> worker cm nullPtr
+               Just x -> withCString x (worker cm)
+                                     )
+    where worker cm cs = checkIntError "magicCompile" m $ magic_compile cm cs
+
+-- 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
+
+-- Does file I/O -> safe
+foreign import ccall safe "magic.h magic_compile"
+  magic_compile :: Ptr CMagic -> CString -> IO CInt
diff --git a/src/Magic/Types.hsc b/src/Magic/Types.hsc
new file mode 100644
--- /dev/null
+++ b/src/Magic/Types.hsc
@@ -0,0 +1,39 @@
+{-  -*- 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-3-Clause
+
+   Maintainer : Philippe <philippedev101\@gmail.com>
+   Stability  : provisional
+   Portability: portable
+
+The core types of the binding: the opaque 'Magic' handle and the 'MagicFlag'
+enumeration (re-exported from "Magic.Data").
+
+Written by John Goerzen.
+-}
+
+module Magic.Types(Magic,
+                   MagicFlag(..))
+where
+import Foreign.ForeignPtr
+import Magic.Data
+import Magic.TypesLL
+
+#include <magic.h>
+
+{- | The magic handle: an opaque cookie obtained from @magicOpen@ and passed to
+the loading and querying functions.
+
+Handles are closed (and their memory freed) automatically when they are
+garbage-collected by Haskell. There is no need to close them explicitly.
+-}
+type Magic = ForeignPtr CMagic
+
diff --git a/src/Magic/TypesLL.hs b/src/Magic/TypesLL.hs
new file mode 100644
--- /dev/null
+++ b/src/Magic/TypesLL.hs
@@ -0,0 +1,27 @@
+{-  -*- 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-3-Clause
+
+   Maintainer : Philippe <philippedev101\@gmail.com>
+   Stability  : provisional
+   Portability: portable
+
+Low-level types for the binding.
+
+Written by John Goerzen.
+-}
+
+module Magic.TypesLL(CMagic)
+where
+
+-- | Phantom type standing in for the C @magic_t@ cookie. It appears only as the
+-- argument of a @ForeignPtr@ (the @Magic@ handle) and has no values of its own.
+data CMagic
diff --git a/src/Magic/Utils.hsc b/src/Magic/Utils.hsc
new file mode 100644
--- /dev/null
+++ b/src/Magic/Utils.hsc
@@ -0,0 +1,74 @@
+{- -*- 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 : John Goerzen,
+   Maintainer : jgoerzen\@complete.org
+   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
+
+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
+       newForeignPtr magic_close ptr
+
+throwErrorIfNull :: String -> Magic -> 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 m = withForeignPtr m
+
+throwError :: String -> Magic -> IO a
+throwError caller m = withMagicPtr m (\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 -> Magic -> IO CInt -> IO ()
+checkIntError caller m action = 
+    do res <- action
+       if res == 0
+          then return ()
+          else throwError caller m
+
+
+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/test/Inittest.hs b/test/Inittest.hs
new file mode 100644
--- /dev/null
+++ b/test/Inittest.hs
@@ -0,0 +1,69 @@
+{-
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+module Inittest(tests) where
+
+import Control.Exception (bracket)
+import qualified Data.ByteString as BS
+import Data.List (isInfixOf, isPrefixOf)
+import System.Directory (getTemporaryDirectory, removeFile)
+import System.FilePath ((</>))
+import Test.HUnit
+
+import Magic
+
+-- | Open a handle with the given flags and load the default database.
+withDefault :: [MagicFlag] -> (Magic -> IO a) -> IO a
+withDefault flags act = do
+    m <- magicOpen flags
+    magicLoadDefault m
+    act m
+
+-- | A small in-memory string is detected as plain text.
+test_textMime :: Test
+test_textMime = TestCase $ do
+    result <- withDefault [MagicMimeType] $ \m ->
+        magicString m "Hello, world!\nThis is just some plain ASCII text.\n"
+    assertBool ("expected a text/* MIME type, got: " ++ show result)
+               ("text/" `isPrefixOf` result)
+
+-- | The textual description of plain text mentions \"text\".
+test_textDescription :: Test
+test_textDescription = TestCase $ do
+    result <- withDefault [MagicNone] $ \m ->
+        magicString m "The quick brown fox jumps over the lazy dog.\n"
+    assertBool ("expected a description mentioning 'text', got: " ++ show result)
+               ("text" `isInfixOf` result)
+
+-- | A file whose contents start with the PNG signature is recognised as a PNG.
+--
+-- Binary content must be detected via a file (raw bytes); passing binary
+-- data through 'magicString' would corrupt the high bytes via the locale
+-- encoding.
+test_pngMime :: Test
+test_pngMime = TestCase $
+    withPngFile $ \pngPath -> do
+        result <- withDefault [MagicMimeType] $ \m -> magicFile m pngPath
+        assertEqual "PNG MIME type" "image/png" result
+  where
+    -- 8-byte PNG signature followed by the start of an IHDR chunk.
+    pngBytes = BS.pack
+        [0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A
+        ,0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52]
+    withPngFile :: (FilePath -> IO b) -> IO b
+    withPngFile act = do
+        dir <- getTemporaryDirectory
+        let pngPath = dir </> "magic-test-sample.png"
+        bracket (BS.writeFile pngPath pngBytes >> return pngPath)
+                removeFile
+                act
+
+tests :: Test
+tests = TestList
+    [ TestLabel "text MIME type"   test_textMime
+    , TestLabel "text description" test_textDescription
+    , TestLabel "PNG MIME type"    test_pngMime
+    ]
diff --git a/test/Tests.hs b/test/Tests.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests.hs
@@ -0,0 +1,13 @@
+{- arch-tag: Tests main file
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+
+This code is under a 3-clause BSD license; see COPYING for details.
+-}
+
+module Tests(tests) where
+
+import Test.HUnit
+import qualified Inittest
+
+tests :: Test
+tests = TestList [TestLabel "init" Inittest.tests]
diff --git a/test/runtests.hs b/test/runtests.hs
new file mode 100644
--- /dev/null
+++ b/test/runtests.hs
@@ -0,0 +1,16 @@
+{- arch-tag: Test runner
+Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>
+-}
+
+module Main (main) where
+
+import Control.Monad (when)
+import System.Exit (exitFailure)
+import Test.HUnit
+
+import Tests
+
+main :: IO ()
+main = do
+    cs <- runTestTT tests
+    when (errors cs > 0 || failures cs > 0) exitFailure
