Skip to content

Instantly share code, notes, and snippets.

@perillo
Created March 5, 2019 11:49
Show Gist options
  • Save perillo/a3af2d5322ce43da8b89167d494b19f9 to your computer and use it in GitHub Desktop.
Save perillo/a3af2d5322ce43da8b89167d494b19f9 to your computer and use it in GitHub Desktop.
// Copyright 2019 Manlio Perillo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package magic detects the mime type of a file. It uses libmagic internally.
//
// The package magic is safe for concurrent use by multiple goroutines.
package magic
// #cgo LDFLAGS: -lmagic
// #include <stdlib.h>
// #include <magic.h>
import "C"
import (
"errors"
"runtime"
"unsafe"
)
// pool manages a list of magic cookies so that they can be used by multiple
// goroutines.
type pool chan C.magic_t
// newPool returns a new pool managing the given list of magic cookies.
func newPool(list []C.magic_t) pool {
pool := make(pool, len(list))
for _, cookie := range list {
pool <- cookie
}
return pool
}
// get acquires a magic cookie.
func (p pool) get() C.magic_t {
return <-p
}
// put returns the magic cookie to the pool.
func (p pool) put(cookie C.magic_t) {
p <- cookie
}
var magicPool pool
func init() {
ncpu := runtime.NumCPU()
n := ncpu * ncpu
list := make([]C.magic_t, n)
for i := 0; i < len(list); i++ {
list[i] = open()
}
magicPool = newPool(list)
}
// DetectFileFormat detects the format of file located at path.
func DetectFileFormat(path string) (string, error) {
cookie := magicPool.get()
defer magicPool.put(cookie)
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
mime := C.magic_file(cookie, cpath)
if mime == nil {
return "", errors.New(errmsg(cookie))
}
return C.GoString(mime), nil
}
// open opens the magic database with the MAGIC_MIME_TYPE and
// MAGIC_PRESERVE_ATIME flags and returns the magic cookie. It panics in case
// of errors.
func open() C.magic_t {
const flags = C.MAGIC_MIME_TYPE | C.MAGIC_PRESERVE_ATIME
// Create the magic cookie, but do not set the flags here since magic_open
// sets errno to EINVAL if a flag is not supported.
cookie := C.magic_open(0)
if cookie == nil {
panic("magic: magic_open failed")
}
// Set the flags now, so that we can report an useful error message if a
// flag (MAGIC_PRESERVE_ATIME) is not supported.
if ret := C.magic_setflags(cookie, flags); ret != 0 {
reason := errmsg(cookie)
C.magic_close(cookie)
panic("magic: magic_setflags failed: " + reason)
}
// Load the magic database using the default location.
if ret := C.magic_load(cookie, nil); ret != 0 {
reason := errmsg(cookie)
C.magic_close(cookie)
panic("magic: magic_load failed: " + reason)
}
return cookie
}
// errmsg returns the textual explanation of the last error.
func errmsg(cookie C.magic_t) string {
return C.GoString(C.magic_error(cookie))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment