github.com/jurelou/go-magic@v0.0.0-20230518182705-f2995a311800/magic.go (about) 1 package magic 2 3 /* 4 #cgo LDFLAGS: -lmagic 5 #cgo !darwin LDFLAGS: -Wl,--as-needed -Wl,--no-undefined 6 #cgo CFLAGS: -std=c99 -fPIC 7 8 #include "functions.h" 9 */ 10 import "C" 11 12 import ( 13 "fmt" 14 "math" 15 "os" 16 "reflect" 17 "runtime" 18 "sort" 19 "strings" 20 "sync" 21 "syscall" 22 "unsafe" 23 ) 24 25 // Separator is a field separator that can be used to split 26 // results when the CONTINUE flag is set causing all valid 27 // matches found by the Magic library to be returned. 28 const Separator string = "\n- " 29 30 // Option represents an option that can be set when creating a new object. 31 type Option func(*Magic) error 32 33 // DoNotStopOnErrors 34 func DoNotStopOnErrors(mgc *Magic) error { 35 mgc.errors = false 36 return nil 37 } 38 39 // DisableAutoload disables autoloading of the Magic database files when 40 // creating a new object. 41 // 42 // This option can be used to prevent the Magic database files from being 43 // loaded from the default location on the filesystem so that the Magic 44 // database can be loaded later manually from a different location using 45 // the Load function, or from a buffer in memory using the LoadBuffers 46 // function. 47 func DisableAutoload(mgc *Magic) error { 48 mgc.autoload = false 49 return nil 50 } 51 52 // WithParameter 53 func WithParameter(parameter int, value int) Option { 54 return func(mgc *Magic) error { 55 return mgc.SetParameter(parameter, value) 56 } 57 } 58 59 // WithFlags 60 func WithFlags(flags int) Option { 61 return func(mgc *Magic) error { 62 return mgc.SetFlags(flags) 63 } 64 } 65 66 // WithFiles 67 func WithFiles(files ...string) Option { 68 return func(mgc *Magic) error { 69 return mgc.Load(files...) 70 } 71 } 72 73 // WithBuffers 74 func WithBuffers(buffers ...[]byte) Option { 75 return func(mgc *Magic) error { 76 return mgc.LoadBuffers(buffers...) 77 } 78 } 79 80 type magic struct { 81 sync.RWMutex 82 // Current flags set (bitmask). 83 flags int 84 // List of the Magic database files currently in-use. 85 paths []string 86 // The Magic database session cookie. 87 cookie C.magic_t 88 // Enable autoloading of the Magic database files. 89 autoload bool 90 // Enable reporting of I/O-related errors as first class errors. 91 errors bool 92 // The Magic database has been loaded successfully. 93 loaded bool 94 } 95 96 // open opens and initializes the Magic library and sets the finalizer 97 // on the object. 98 func open() (*Magic, error) { 99 // Can only fail allocating memory in this particular case. 100 cMagic := C.magic_open_wrapper(C.int(NONE)) 101 if cMagic == nil { 102 return nil, &Error{int(syscall.ENOMEM), "failed to initialize Magic library"} 103 } 104 mgc := &Magic{&magic{flags: NONE, cookie: cMagic, autoload: true, errors: true}} 105 runtime.SetFinalizer(mgc.magic, (*magic).close) 106 return mgc, nil 107 } 108 109 // close closes the Magic library and clears finalizer set on the object. 110 func (m *magic) close() { 111 if m != nil && m.cookie != nil { 112 // This will free resources on the Magic library side. 113 C.magic_close_wrapper(m.cookie) 114 m.paths = []string{} 115 m.cookie = nil 116 } 117 runtime.SetFinalizer(m, nil) 118 } 119 120 // error retrieves an error from the Magic library. 121 func (m *magic) error() error { 122 if cString := C.magic_error_wrapper(m.cookie); cString != nil { 123 // Depending on the version of the Magic library, 124 // the error reporting facilities can fail and 125 // either yield no results or return the "(null)" 126 // string instead. Often this would indicate that 127 // an older version of the Magic library is in use. 128 s := C.GoString(cString) 129 if s == "" || s == "(null)" { 130 return &Error{-1, "empty or invalid error message"} 131 } 132 return &Error{int(C.magic_errno_wrapper(m.cookie)), s} 133 } 134 return &Error{-1, "an unknown error has occurred"} 135 } 136 137 // Magic represents the Magic library. 138 type Magic struct { 139 *magic 140 } 141 142 // New opens and initializes the Magic library. 143 // 144 // Optionally, a multiple distinct the Magic database files can 145 // be provided to load, otherwise a default database (usually 146 // available system-wide) will be loaded. 147 // 148 // Alternatively, the "MAGIC" environment variable can be used 149 // to name any desired the Magic database files to be loaded, but 150 // it must be set prior to calling this function for it to take 151 // effect. 152 // 153 // Remember to call Close to release initialized resources 154 // and close currently opened the Magic library, or use Open 155 // which will ensure that Close is called once the closure 156 // finishes. 157 func New(options ...Option) (*Magic, error) { 158 mgc, err := open() 159 if err != nil { 160 return nil, err 161 } 162 163 if s := os.Getenv("MAGIC_DO_NOT_AUTOLOAD"); s != "" { 164 mgc.autoload = false 165 } 166 if s := os.Getenv("MAGIC_DO_NOT_STOP_ON_ERROR"); s != "" { 167 mgc.errors = false 168 } 169 170 for _, option := range options { 171 if err := option(mgc); err != nil { 172 mgc.close() 173 return nil, err 174 } 175 } 176 177 if mgc.autoload && !mgc.loaded { 178 if err := mgc.Load(); err != nil { 179 return nil, err 180 } 181 } 182 return mgc, nil 183 } 184 185 /// Must 186 func Must(magic *Magic, err error) *Magic { 187 if err != nil { 188 panic(err) 189 } 190 return magic 191 } 192 193 // Close releases all initialized resources and closes 194 // currently open the Magic library. 195 func (mgc *Magic) Close() { 196 mgc.Lock() 197 defer mgc.Unlock() 198 mgc.close() 199 } 200 201 // IsOpen returns true if the Magic library is currently 202 // open, or false otherwise. 203 func (mgc *Magic) IsOpen() bool { 204 mgc.RLock() 205 defer mgc.RUnlock() 206 return verifyOpen(mgc) == nil 207 } 208 209 // IsClosed returns true if the Magic library has 210 // been closed, or false otherwise. 211 func (mgc *Magic) IsClosed() bool { 212 return !mgc.IsOpen() 213 } 214 215 // HasLoaded returns true if the Magic library has 216 // been loaded successfully, or false otherwise. 217 func (mgc *Magic) HasLoaded() bool { 218 mgc.RLock() 219 defer mgc.RUnlock() 220 return verifyLoaded(mgc) == nil 221 } 222 223 // String returns a string representation of the Magic type. 224 func (mgc *Magic) String() string { 225 mgc.RLock() 226 defer mgc.RUnlock() 227 s := fmt.Sprintf("Magic{flags:%d paths:%v open:%t loaded:%t}", mgc.flags, mgc.paths, mgc.IsOpen(), mgc.HasLoaded()) 228 return s 229 } 230 231 // Paths returns a slice containing fully-qualified path for each 232 // of the Magic database files that was loaded and is currently 233 // in use. 234 // 235 // Optionally, if the "MAGIC" environment variable is present, 236 // then each path from it will be taken into the account and the 237 // value that this function returns will be updated accordingly. 238 func (mgc *Magic) Paths() ([]string, error) { 239 mgc.Lock() 240 defer mgc.Unlock() 241 242 if err := verifyOpen(mgc); err != nil { 243 return []string{}, err 244 } 245 246 // Respect the "MAGIC" environment variable, if present. 247 if len(mgc.paths) > 0 && os.Getenv("MAGIC") == "" { 248 return mgc.paths, nil 249 } 250 paths := C.GoString(C.magic_getpath_wrapper()) 251 return strings.Split(paths, ":"), nil 252 } 253 254 // Parameter 255 func (mgc *Magic) Parameter(parameter int) (int, error) { 256 mgc.Lock() 257 defer mgc.Unlock() 258 259 if err := verifyOpen(mgc); err != nil { 260 return -1, err 261 } 262 263 var value int 264 p := unsafe.Pointer(&value) 265 266 cResult, err := C.magic_getparam_wrapper(mgc.cookie, C.int(parameter), p) 267 if cResult < 0 && err != nil { 268 if errno := err.(syscall.Errno); errno == syscall.EINVAL { 269 return -1, &Error{int(errno), "unknown or invalid parameter specified"} 270 } 271 return -1, mgc.error() 272 } 273 return value, nil 274 } 275 276 // SetParameter 277 func (mgc *Magic) SetParameter(parameter int, value int) error { 278 mgc.Lock() 279 defer mgc.Unlock() 280 281 if err := verifyOpen(mgc); err != nil { 282 return err 283 } 284 285 p := unsafe.Pointer(&value) 286 287 cResult, err := C.magic_setparam_wrapper(mgc.cookie, C.int(parameter), p) 288 if cResult < 0 && err != nil { 289 errno := err.(syscall.Errno) 290 switch errno { 291 case syscall.EINVAL: 292 return &Error{int(errno), "unknown or invalid parameter specified"} 293 case syscall.EOVERFLOW: 294 return &Error{int(errno), "invalid parameter value specified"} 295 default: 296 return mgc.error() 297 } 298 } 299 return nil 300 } 301 302 // Flags returns a value (bitmask) representing current flags set. 303 func (mgc *Magic) Flags() (int, error) { 304 mgc.RLock() 305 defer mgc.RUnlock() 306 307 if err := verifyOpen(mgc); err != nil { 308 return -1, err 309 } 310 311 cRv, err := C.magic_getflags_wrapper(mgc.cookie) 312 if cRv < 0 && err != nil { 313 if err.(syscall.Errno) == syscall.ENOSYS { 314 return mgc.flags, nil 315 } 316 return -1, mgc.error() 317 } 318 return int(cRv), nil 319 } 320 321 // SetFlags sets the flags to the new value (bitmask). 322 // 323 // Depending on which flags are current set the results and/or 324 // behavior of the Magic library will change accordingly. 325 func (mgc *Magic) SetFlags(flags int) error { 326 mgc.Lock() 327 defer mgc.Unlock() 328 329 if err := verifyOpen(mgc); err != nil { 330 return err 331 } 332 333 cResult, err := C.magic_setflags_wrapper(mgc.cookie, C.int(flags)) 334 if cResult < 0 && err != nil { 335 if errno := err.(syscall.Errno); errno == syscall.EINVAL { 336 return &Error{int(errno), "unknown or invalid flag specified"} 337 } 338 return mgc.error() 339 } 340 mgc.flags = flags 341 return nil 342 } 343 344 // FlagsSlice returns a slice containing each distinct flag that 345 // is currently set and included as a part of the current value 346 // (bitmask) of flags. 347 // 348 // Results are sorted in an ascending order. 349 func (mgc *Magic) FlagsSlice() ([]int, error) { 350 mgc.RLock() 351 defer mgc.RUnlock() 352 353 if err := verifyOpen(mgc); err != nil { 354 return []int{}, err 355 } 356 if mgc.flags == 0 { 357 return []int{0}, nil 358 } 359 360 var ( 361 n int 362 flags []int 363 ) 364 365 // Split current value (bitmask) into a list 366 // of distinct flags (bits) currently set. 367 for i := mgc.flags; i > 0; i -= n { 368 n = int(math.Log2(float64(i))) 369 n = int(math.Pow(2, float64(n))) 370 flags = append(flags, n) 371 } 372 sort.Ints(flags) 373 return flags, nil 374 } 375 376 // Load 377 func (mgc *Magic) Load(files ...string) error { 378 mgc.Lock() 379 defer mgc.Unlock() 380 381 if err := verifyOpen(mgc); err != nil { 382 return err 383 } 384 // Clear paths. To be set again when the Magic 385 // database files are successfully loaded. 386 mgc.paths = []string{} 387 388 var cFiles *C.char 389 defer C.free(unsafe.Pointer(cFiles)) 390 391 // Assemble the list of custom database Magic files into a 392 // colon-separated list that is required by the Magic library, 393 // otherwise defer to the default list of paths provided by 394 // the Magic library. 395 if len(files) > 0 { 396 cFiles = C.CString(strings.Join(files, ":")) 397 } else { 398 cFiles = C.magic_getpath_wrapper() 399 } 400 401 if cRv := C.magic_load_wrapper(mgc.cookie, cFiles, C.int(mgc.flags)); cRv < 0 { 402 mgc.loaded = false 403 return mgc.error() 404 } 405 mgc.loaded = true 406 mgc.paths = strings.Split(C.GoString(cFiles), ":") 407 return nil 408 } 409 410 // LoadBuffers 411 func (mgc *Magic) LoadBuffers(buffers ...[]byte) error { 412 mgc.Lock() 413 defer mgc.Unlock() 414 415 if err := verifyOpen(mgc); err != nil { 416 return err 417 } 418 419 var ( 420 empty []byte 421 p *unsafe.Pointer 422 s *C.size_t 423 ) 424 // Clear paths. To be set again when the Magic 425 // database files are successfully loaded. 426 mgc.paths = []string{} 427 428 cSize := C.size_t(len(buffers)) 429 cPointers := make([]uintptr, cSize) 430 cSizes := make([]C.size_t, cSize) 431 432 for i := range buffers { 433 // An attempt to load the Magic database from a number of 434 // buffers in memory where a single buffer is empty would 435 // result in a failure. 436 cPointers[i] = uintptr(unsafe.Pointer(&empty)) 437 if s := len(buffers[i]); s > 0 { 438 cPointers[i] = uintptr(unsafe.Pointer(&buffers[i][0])) 439 cSizes[i] = C.size_t(s) 440 } 441 } 442 443 if cSize > 0 { 444 p = (*unsafe.Pointer)(unsafe.Pointer(&cPointers[0])) 445 s = (*C.size_t)(unsafe.Pointer(&cSizes[0])) 446 } 447 448 if cRv := C.magic_load_buffers_wrapper(mgc.cookie, p, s, cSize, C.int(mgc.flags)); cRv < 0 { 449 mgc.loaded = false 450 // Loading a compiled Magic database from a buffer in memory can 451 // often cause failure, sadly there isn't a proper error messages 452 // in some of the cases, thus the assumtion is that it failed 453 // at it couldn't be loaded, whatever the reason. 454 if cString := C.magic_error_wrapper(mgc.cookie); cString != nil { 455 return &Error{-1, C.GoString(cString)} 456 } 457 return &Error{-1, "unable to load Magic database"} 458 } 459 mgc.loaded = true 460 return nil 461 } 462 463 // Compile 464 func (mgc *Magic) Compile(file string) error { 465 mgc.RLock() 466 defer mgc.RUnlock() 467 468 if err := verifyOpen(mgc); err != nil { 469 return err 470 } 471 472 cFile := C.CString(file) 473 defer C.free(unsafe.Pointer(cFile)) 474 475 if cRv := C.magic_compile_wrapper(mgc.cookie, cFile, C.int(mgc.flags)); cRv < 0 { 476 return mgc.error() 477 } 478 return nil 479 } 480 481 // Check 482 func (mgc *Magic) Check(file string) (bool, error) { 483 mgc.RLock() 484 defer mgc.RUnlock() 485 486 if err := verifyOpen(mgc); err != nil { 487 return false, err 488 } 489 490 cFile := C.CString(file) 491 defer C.free(unsafe.Pointer(cFile)) 492 493 if cRv := C.magic_check_wrapper(mgc.cookie, cFile, C.int(mgc.flags)); cRv < 0 { 494 return false, mgc.error() 495 } 496 return true, nil 497 } 498 499 // File 500 func (mgc *Magic) File(file string) (string, error) { 501 mgc.RLock() 502 defer mgc.RUnlock() 503 504 if err := verifyOpen(mgc); err != nil { 505 return "", err 506 } 507 if err := verifyLoaded(mgc); err != nil { 508 return "", err 509 } 510 511 cFile := C.CString(file) 512 defer C.free(unsafe.Pointer(cFile)) 513 514 var cString *C.char 515 516 flagsSaveAndRestore(mgc, func() { 517 cString = C.magic_file_wrapper(mgc.cookie, cFile, C.int(mgc.flags)) 518 }) 519 if cString == nil { 520 // Handle the case when the "ERROR" flag is set regardless 521 // of the current version of the Magic library. 522 // 523 // Prior to version 5.15 the correct behavior that concerns 524 // the following IEEE 1003.1 standards was broken: 525 // 526 // http://pubs.opengroup.org/onlinepubs/007904975/utilities/file.html 527 // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/file.html 528 // 529 // This is an attempt to mitigate the problem and correct 530 // it to achieve the desired behavior as per the standards. 531 if mgc.errors || mgc.flags&ERROR != 0 { 532 return "", mgc.error() 533 } 534 cString = C.magic_error_wrapper(mgc.cookie) 535 } 536 return errorOrString(mgc, cString) 537 } 538 539 // Buffer 540 func (mgc *Magic) Buffer(buffer []byte) (string, error) { 541 mgc.RLock() 542 defer mgc.RUnlock() 543 544 if err := verifyOpen(mgc); err != nil { 545 return "", err 546 } 547 if err := verifyLoaded(mgc); err != nil { 548 return "", err 549 } 550 551 var ( 552 cString *C.char 553 p unsafe.Pointer 554 ) 555 556 cSize := C.size_t(len(buffer)) 557 if cSize > 0 { 558 p = unsafe.Pointer(&buffer[0]) 559 } 560 561 flagsSaveAndRestore(mgc, func() { 562 cString = C.magic_buffer_wrapper(mgc.cookie, p, cSize, C.int(mgc.flags)) 563 }) 564 return errorOrString(mgc, cString) 565 } 566 567 // Descriptor 568 func (mgc *Magic) Descriptor(fd uintptr) (string, error) { 569 mgc.RLock() 570 defer mgc.RUnlock() 571 572 if err := verifyOpen(mgc); err != nil { 573 return "", err 574 } 575 if err := verifyLoaded(mgc); err != nil { 576 return "", err 577 } 578 579 var ( 580 err error 581 cString *C.char 582 ) 583 584 flagsSaveAndRestore(mgc, func() { 585 cString, err = C.magic_descriptor_wrapper(mgc.cookie, C.int(fd), C.int(mgc.flags)) 586 }) 587 if err != nil { 588 if errno := err.(syscall.Errno); errno == syscall.EBADF { 589 return "", &Error{int(errno), "bad file descriptor"} 590 } 591 } 592 return errorOrString(mgc, cString) 593 } 594 595 // Open 596 func Open(f func(*Magic) error, options ...Option) (err error) { 597 var ok bool 598 599 if f == nil || reflect.TypeOf(f).Kind() != reflect.Func { 600 return &Error{-1, "not a function or nil pointer"} 601 } 602 603 mgc, err := New(options...) 604 if err != nil { 605 return err 606 } 607 defer mgc.Close() 608 609 // Make sure to return a proper error should there 610 // be any failure originating from within the closure. 611 defer func() { 612 if r := recover(); r != nil { 613 err, ok = r.(error) 614 if !ok { 615 err = &Error{-1, fmt.Sprintf("%v", r)} 616 } 617 } 618 }() 619 return f(mgc) 620 } 621 622 // Compile 623 func Compile(file string) error { 624 mgc, err := open() 625 if err != nil { 626 return err 627 } 628 defer mgc.close() 629 return mgc.Compile(file) 630 } 631 632 // Check 633 func Check(file string) (bool, error) { 634 mgc, err := open() 635 if err != nil { 636 return false, err 637 } 638 defer mgc.close() 639 return mgc.Check(file) 640 } 641 642 // Version returns the Magic library version as an integer 643 // value in the format "XYY", where X is the major version 644 // and Y is the minor version number. 645 func Version() int { 646 return int(C.magic_version_wrapper()) 647 } 648 649 // VersionString returns the Magic library version as 650 // a string in the format "X.YY". 651 func VersionString() string { 652 v := Version() 653 return fmt.Sprintf("%d.%02d", v/100, v%100) 654 } 655 656 // VersionSlice returns a slice containing values of both the 657 // major and minor version numbers separated from one another. 658 func VersionSlice() []int { 659 v := Version() 660 return []int{v / 100, v % 100} 661 } 662 663 // FileMime returns MIME identification (both the MIME type 664 // and MIME encoding), rather than a textual description, 665 // for the named file. 666 func FileMime(file string, options ...Option) (string, error) { 667 mgc, err := New(options...) 668 if err != nil { 669 return "", err 670 } 671 defer mgc.Close() 672 673 if err := mgc.SetFlags(MIME); err != nil { 674 return "", err 675 } 676 return mgc.File(file) 677 } 678 679 // FileType returns MIME type only, rather than a textual 680 // description, for the named file. 681 func FileType(file string, options ...Option) (string, error) { 682 mgc, err := New(options...) 683 if err != nil { 684 return "", err 685 } 686 defer mgc.Close() 687 688 if err := mgc.SetFlags(MIME_TYPE); err != nil { 689 return "", err 690 } 691 return mgc.File(file) 692 } 693 694 // FileEncoding returns MIME encoding only, rather than a textual 695 // description, for the content of the buffer. 696 func FileEncoding(file string, options ...Option) (string, error) { 697 mgc, err := New(options...) 698 if err != nil { 699 return "", err 700 } 701 defer mgc.Close() 702 703 if err := mgc.SetFlags(MIME_ENCODING); err != nil { 704 return "", err 705 } 706 return mgc.File(file) 707 } 708 709 // BufferMime returns MIME identification (both the MIME type 710 // and MIME encoding), rather than a textual description, 711 // for the content of the buffer. 712 func BufferMime(buffer []byte, options ...Option) (string, error) { 713 mgc, err := New(options...) 714 if err != nil { 715 return "", err 716 } 717 defer mgc.Close() 718 719 if err := mgc.SetFlags(MIME); err != nil { 720 return "", err 721 } 722 return mgc.Buffer(buffer) 723 } 724 725 // BufferType returns MIME type only, rather than a textual 726 // description, for the content of the buffer. 727 func BufferType(buffer []byte, options ...Option) (string, error) { 728 mgc, err := New(options...) 729 if err != nil { 730 return "", err 731 } 732 defer mgc.Close() 733 734 if err := mgc.SetFlags(MIME_TYPE); err != nil { 735 return "", err 736 } 737 return mgc.Buffer(buffer) 738 } 739 740 // BufferEncoding returns MIME encoding only, rather than a textual 741 // description, for the content of the buffer. 742 func BufferEncoding(buffer []byte, options ...Option) (string, error) { 743 mgc, err := New(options...) 744 if err != nil { 745 return "", err 746 } 747 defer mgc.Close() 748 749 if err := mgc.SetFlags(MIME_ENCODING); err != nil { 750 return "", err 751 } 752 return mgc.Buffer(buffer) 753 } 754 755 func verifyOpen(mgc *Magic) error { 756 if mgc != nil && mgc.cookie != nil { 757 return nil 758 } 759 return &Error{int(syscall.EFAULT), "Magic library is not open"} 760 } 761 762 func verifyLoaded(mgc *Magic) error { 763 // Magic database can only ever be loaded 764 // if the Magic library is currently open. 765 if err := verifyOpen(mgc); err == nil && mgc.loaded { 766 return nil 767 } 768 return &Error{-1, "Magic database not loaded"} 769 } 770 771 func flagsSaveAndRestore(mgc *Magic, f func()) { 772 var flags int 773 774 flags, mgc.flags = mgc.flags, mgc.flags|RAW 775 // Make sure to set the "ERROR" flag so that any 776 // I/O-related errors will become first class 777 // errors reported back by the Magic library. 778 if mgc.errors { 779 mgc.flags |= ERROR 780 } 781 782 ok := mgc.flags&CONTINUE != 0 || mgc.flags&ERROR != 0 783 if ok { 784 C.magic_setflags_wrapper(mgc.cookie, C.int(mgc.flags)) 785 } 786 defer func() { 787 if ok && flags > 0 { 788 C.magic_setflags_wrapper(mgc.cookie, C.int(mgc.flags)) 789 } 790 }() 791 mgc.flags = flags 792 f() 793 } 794 795 func errorOrString(mgc *Magic, cString *C.char) (string, error) { 796 if cString == nil { 797 return "", &Error{-1, "unknown result or nil pointer"} 798 } 799 s := C.GoString(cString) 800 if s != "" { 801 return s, nil 802 } 803 if s == "???" || s == "(null)" { 804 // The Magic flag that support primarily files e.g., 805 // MAGIC_EXTENSION, etc., would not return a meaningful 806 // value for directories and special files, and such. 807 // Thus, it's better to return an empty string to 808 // indicate lack of results, rather than a confusing 809 // string consisting of three questions marks. 810 if mgc.flags&EXTENSION != 0 { 811 return "", nil 812 } 813 // Depending on the version of the Magic library 814 // the magic_file() function can fail and either 815 // yield no results or return the "(null)" string 816 // instead. Often this would indicate that an 817 // older version of the Magic library is in use. 818 return "", &Error{-1, "empty or invalid result"} 819 } 820 return "", mgc.error() 821 }