summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:52:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:52:51 +0000
commit71507ca5d2410b11889ca963fafcd1bcad5044c3 (patch)
tree17e6d07243d49e29e4b75887e0d07f24ec2b66e8 /src
parentInitial commit. (diff)
downloadfile-upstream.tar.xz
file-upstream.zip
Adding upstream version 1:5.44.upstream/1%5.44upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/BNF151
-rw-r--r--src/Makefile.am30
-rw-r--r--src/Makefile.in900
-rw-r--r--src/apprentice.c3728
-rw-r--r--src/apptype.c169
-rw-r--r--src/ascmagic.c395
-rw-r--r--src/asctime_r.c19
-rw-r--r--src/asprintf.c45
-rw-r--r--src/buffer.c88
-rw-r--r--src/cdf.c1676
-rw-r--r--src/cdf.h353
-rw-r--r--src/cdf.mk3
-rw-r--r--src/cdf_time.c203
-rw-r--r--src/compress.c1215
-rw-r--r--src/ctime_r.c19
-rw-r--r--src/der.c458
-rw-r--r--src/der.h28
-rw-r--r--src/dprintf.c58
-rw-r--r--src/elfclass.h82
-rw-r--r--src/encoding.c658
-rw-r--r--src/file.c856
-rw-r--r--src/file.h712
-rw-r--r--src/file_opts.h89
-rw-r--r--src/fmtcheck.c254
-rw-r--r--src/fsmagic.c435
-rw-r--r--src/funcs.c918
-rw-r--r--src/getline.c104
-rw-r--r--src/getopt_long.c498
-rw-r--r--src/gmtime_r.c19
-rw-r--r--src/is_csv.c196
-rw-r--r--src/is_json.c500
-rw-r--r--src/is_tar.c179
-rw-r--r--src/localtime_r.c19
-rw-r--r--src/magic.c680
-rw-r--r--src/magic.h.in163
-rw-r--r--src/memtest.c148
-rw-r--r--src/mygetopt.h68
-rw-r--r--src/pread.c23
-rw-r--r--src/print.c365
-rw-r--r--src/readcdf.c682
-rw-r--r--src/readelf.c1902
-rw-r--r--src/readelf.h545
-rw-r--r--src/seccomp.c289
-rw-r--r--src/softmagic.c2506
-rw-r--r--src/strcasestr.c84
-rw-r--r--src/strlcat.c61
-rw-r--r--src/strlcpy.c57
-rw-r--r--src/tar.h73
-rw-r--r--src/vasprintf.c650
49 files changed, 23353 insertions, 0 deletions
diff --git a/src/BNF b/src/BNF
new file mode 100644
index 0000000..243532f
--- /dev/null
+++ b/src/BNF
@@ -0,0 +1,151 @@
+This is a first attempt to document the grammar used by magic(5), with
+hopes of eventually incorporating something like this into the manpage.
+
+Note: Currently, the parser varies slightly from this, but only in
+very minor ways, e.g., the strflags maybe separated by '/' characters
+and at most one strcount is allowed; likewise for regflags.
+
+------------------------------------------------------------------------
+magic = 1*query
+
+query = line *( 1*level line )
+
+level = ">" ;; Increment the level by 1.
+ ;; The first line of a query is at level 0.
+
+line = offset HWS type HWS test HWS message EOL
+
+------------------------------------------------------------------------
+offset = absoffset | reloffset | indoffset
+ ;; The offset in the file at which to apply
+ ;; the <test>.
+
+absoffset = NUMBER ;; An absolute offset from the start of the file.
+
+reloffset = "&" NUMBER ;; The offset relative to the last match offset
+ ;; at one level up.
+ ;; Not allowed at level == 0.
+
+indoffset = indoff | relindoff
+
+indoff = "(" offset1 [ "." size ] [ op disp ] ")"
+ ;; Read the file at <offset1> of width <size>.
+ ;; If size is not specified, assume a long.
+ ;; If <op> is given, then preform that
+ ;; operation on the result and the <disp>.
+
+offset1 = absoffset | reloffset
+
+size = byte | leshort | beshort | lelong | belong | melong
+
+byte = "B" | "b" | "C" | "c" ;; A one-byte value.
+leshort = "s" | "h" ;; A two-byte little-endian value.
+beshort = "S" | "H" ;; A two-byte big-endian value.
+lelong = "l" ;; A four-byte little-endian value.
+belong = "L" ;; A four-byte big-endian value.
+melong = "m" ;; A four-byte middle-endian value.
+
+op = [ invert ] ( "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" )
+
+invert = "~" ;; Flip the bits on result of the <op>.
+
+disp = NUMBER | memvalue
+
+memvalue = "(" NUMBER ")"
+ ;; NUMBER is interpreted as an absolute or
+ ;; relative offset matching that of <offset1>.
+ ;; Read the file at the resulting offset with
+ ;; the same size as <offset1>
+
+relindoff = "&" indoff ;; add <indoff> to the last match offset at
+ ;; one level up.
+
+------------------------------------------------------------------------
+type = [ unsigned ] ( numeric | strtype | default )
+
+unsigned = "u" ;; The value is unsigned.
+ ;; This affects the sign extension of numeric
+ ;; types and the '<' and '>' compares. It is
+ ;; intended for numeric types, but allowed on
+ ;; all types.
+
+numeric = ( numtype | datatype ) [ nummask ]
+
+numtype = byte | short | long | quad
+
+byte = "byte"
+short = "short" | "beshort" | "leshort"
+long = "long" | "lelong" | "belong" | "melong"
+quad = "quad" | "lequad" | "bequad"
+
+datetype = udate32 | ldate32 | udate64 | ldate64
+
+udate32 = "date" | "bedate" | "ledate" | "medate" ;; UTC dates
+ldate32 = "ldate" | "beldate" | "leldate" | "meldate" ;; local dates
+udate64 = "qdate" | "leqdate" | "beqdate" ;; UTC dates
+ldate64 = "qldate" | "leqldate" | "beqldate" ;; local dates
+
+nummask = op NUMBER
+
+strtype = regex | search | string8 | string16
+
+regex = "regex" [ "/" 1*regflag ]
+
+regflag = "c" | "s" | linecnt
+
+linecnt = NUMBER ;; The number of lines to search. If this
+ ;; is missing or zero, the rest of the
+ ;; file is searched.
+
+search = "string" [ "/" 1*srchflag ]
+
+srchflag = strflag | srchcnt
+
+srchcnt = NUMBER ;; The number of search tries. If this
+ ;; is missing or zero, the rest of the
+ ;; file is searched.
+
+string8 = ( "string" | "pstring" ) [ "/" 1*strflag ]
+
+strflag = "b" | "B" | "c" | "C"
+
+string16 = "bestring16" | "lestring16"
+
+default = "default" ;; This is intended to be used with the
+ ;; <truetest> ("x" below). It is matched if
+ ;; there has been no previous match at its
+ ;; level or none since the last default at
+ ;; that level. It is useful for implementing
+ ;; switch-like and if/else constructions.
+
+------------------------------------------------------------------------
+test = numtest | strtest | truetest
+ ;; Test to preform on <type> read from file.
+
+numtest = [ compare ] NUMBER ;; If compare is missing, "=" is assumed.
+
+strtest = [ compare ] STRING ;; If compare is missing, "=" is assumed.
+ ;; Note: If the STRING begins with a <compare>
+ ;; character, the <compare> field cannot be
+ ;; omitted.
+
+compare = "=" | "!" | "<" | ">" | "&" | "^"
+
+truetest = "x" ;; This always returns true.
+ ;; To test for the string "x" use "=x".
+
+------------------------------------------------------------------------
+message = [ nospflag ] ( STRING | FMT_STRING )
+ ;; Message to print if test result is true.
+
+nospflag = %x08 | "\\b" ;; Do not insert a space before the message.
+ ;; By default, messages are separated by a " ".
+
+------------------------------------------------------------------------
+HWS = <horizontal white space>
+EOL = <end of line marker>
+NUMBER = <C-style unsigned number>
+STRING = <C-style string without delimiting quotes>
+FMTSTR = <printf format string with exactly one % construct>
+
+------------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..96749b5
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,30 @@
+MAGIC = $(pkgdatadir)/magic
+lib_LTLIBRARIES = libmagic.la
+nodist_include_HEADERS = magic.h
+
+bin_PROGRAMS = file
+
+AM_CPPFLAGS = -DMAGIC='"$(MAGIC)"'
+AM_CFLAGS = $(CFLAG_VISIBILITY) @WARNINGS@
+
+libmagic_la_SOURCES = buffer.c magic.c apprentice.c softmagic.c ascmagic.c \
+ encoding.c compress.c is_csv.c is_json.c is_tar.c readelf.c print.c \
+ fsmagic.c funcs.c file.h readelf.h tar.h apptype.c der.c der.h \
+ file_opts.h elfclass.h mygetopt.h cdf.c cdf_time.c readcdf.c cdf.h
+libmagic_la_LDFLAGS = -no-undefined -version-info 1:0:0
+if MINGW
+MINGWLIBS = -lgnurx -lshlwapi
+else
+MINGWLIBS =
+endif
+libmagic_la_LIBADD = -lm $(LTLIBOBJS) $(MINGWLIBS)
+
+file_SOURCES = file.c seccomp.c
+file_LDADD = libmagic.la -lm
+CLEANFILES = magic.h
+EXTRA_DIST = magic.h.in cdf.mk BNF memtest.c
+HDR= $(top_srcdir)/src/magic.h.in
+BUILT_SOURCES = magic.h
+
+magic.h: ${HDR}
+ sed -e "s/X.YY/$$(echo @VERSION@ | tr -d .)/" < ${HDR} > $@
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..155034b
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,900 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = file$(EXEEXT)
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/visibility.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(includedir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libmagic_la_DEPENDENCIES = $(LTLIBOBJS) $(am__DEPENDENCIES_1)
+am_libmagic_la_OBJECTS = buffer.lo magic.lo apprentice.lo softmagic.lo \
+ ascmagic.lo encoding.lo compress.lo is_csv.lo is_json.lo \
+ is_tar.lo readelf.lo print.lo fsmagic.lo funcs.lo apptype.lo \
+ der.lo cdf.lo cdf_time.lo readcdf.lo
+libmagic_la_OBJECTS = $(am_libmagic_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libmagic_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libmagic_la_LDFLAGS) $(LDFLAGS) -o $@
+am_file_OBJECTS = file.$(OBJEXT) seccomp.$(OBJEXT)
+file_OBJECTS = $(am_file_OBJECTS)
+file_DEPENDENCIES = libmagic.la
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = $(DEPDIR)/asctime_r.Plo $(DEPDIR)/asprintf.Plo \
+ $(DEPDIR)/ctime_r.Plo $(DEPDIR)/dprintf.Plo \
+ $(DEPDIR)/fmtcheck.Plo $(DEPDIR)/getline.Plo \
+ $(DEPDIR)/getopt_long.Plo $(DEPDIR)/gmtime_r.Plo \
+ $(DEPDIR)/localtime_r.Plo $(DEPDIR)/pread.Plo \
+ $(DEPDIR)/strcasestr.Plo $(DEPDIR)/strlcat.Plo \
+ $(DEPDIR)/strlcpy.Plo $(DEPDIR)/vasprintf.Plo \
+ ./$(DEPDIR)/apprentice.Plo ./$(DEPDIR)/apptype.Plo \
+ ./$(DEPDIR)/ascmagic.Plo ./$(DEPDIR)/buffer.Plo \
+ ./$(DEPDIR)/cdf.Plo ./$(DEPDIR)/cdf_time.Plo \
+ ./$(DEPDIR)/compress.Plo ./$(DEPDIR)/der.Plo \
+ ./$(DEPDIR)/encoding.Plo ./$(DEPDIR)/file.Po \
+ ./$(DEPDIR)/fsmagic.Plo ./$(DEPDIR)/funcs.Plo \
+ ./$(DEPDIR)/is_csv.Plo ./$(DEPDIR)/is_json.Plo \
+ ./$(DEPDIR)/is_tar.Plo ./$(DEPDIR)/magic.Plo \
+ ./$(DEPDIR)/print.Plo ./$(DEPDIR)/readcdf.Plo \
+ ./$(DEPDIR)/readelf.Plo ./$(DEPDIR)/seccomp.Po \
+ ./$(DEPDIR)/softmagic.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmagic_la_SOURCES) $(file_SOURCES)
+DIST_SOURCES = $(libmagic_la_SOURCES) $(file_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(nodist_include_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ asctime_r.c asprintf.c ctime_r.c dprintf.c fmtcheck.c \
+ getline.c getopt_long.c gmtime_r.c localtime_r.c pread.c \
+ strcasestr.c strlcat.c strlcpy.c vasprintf.c
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkgdatadir = @pkgdatadir@
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CFLAG_VISIBILITY = @CFLAG_VISIBILITY@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+HAVE_VISIBILITY = @HAVE_VISIBILITY@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MINGW = @MINGW@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+WARNINGS = @WARNINGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+fsect = @fsect@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+MAGIC = $(pkgdatadir)/magic
+lib_LTLIBRARIES = libmagic.la
+nodist_include_HEADERS = magic.h
+AM_CPPFLAGS = -DMAGIC='"$(MAGIC)"'
+AM_CFLAGS = $(CFLAG_VISIBILITY) @WARNINGS@
+libmagic_la_SOURCES = buffer.c magic.c apprentice.c softmagic.c ascmagic.c \
+ encoding.c compress.c is_csv.c is_json.c is_tar.c readelf.c print.c \
+ fsmagic.c funcs.c file.h readelf.h tar.h apptype.c der.c der.h \
+ file_opts.h elfclass.h mygetopt.h cdf.c cdf_time.c readcdf.c cdf.h
+
+libmagic_la_LDFLAGS = -no-undefined -version-info 1:0:0
+@MINGW_FALSE@MINGWLIBS =
+@MINGW_TRUE@MINGWLIBS = -lgnurx -lshlwapi
+libmagic_la_LIBADD = -lm $(LTLIBOBJS) $(MINGWLIBS)
+file_SOURCES = file.c seccomp.c
+file_LDADD = libmagic.la -lm
+CLEANFILES = magic.h
+EXTRA_DIST = magic.h.in cdf.mk BNF memtest.c
+HDR = $(top_srcdir)/src/magic.h.in
+BUILT_SOURCES = magic.h
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmagic.la: $(libmagic_la_OBJECTS) $(libmagic_la_DEPENDENCIES) $(EXTRA_libmagic_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libmagic_la_LINK) -rpath $(libdir) $(libmagic_la_OBJECTS) $(libmagic_la_LIBADD) $(LIBS)
+
+file$(EXEEXT): $(file_OBJECTS) $(file_DEPENDENCIES) $(EXTRA_file_DEPENDENCIES)
+ @rm -f file$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(file_OBJECTS) $(file_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/asctime_r.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/asprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/ctime_r.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/dprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/fmtcheck.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/getline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/getopt_long.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/gmtime_r.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/localtime_r.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/pread.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strcasestr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strlcat.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strlcpy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/vasprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apprentice.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apptype.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ascmagic.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cdf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cdf_time.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compress.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/der.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encoding.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsmagic.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/funcs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/is_csv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/is_json.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/is_tar.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/magic.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readcdf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readelf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seccomp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/softmagic.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-nodist_includeHEADERS: $(nodist_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(nodist_include_HEADERS)'; test -n "$(includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+ done
+
+uninstall-nodist_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(nodist_include_HEADERS)'; test -n "$(includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+install-binPROGRAMS: install-libLTLIBRARIES
+
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f $(DEPDIR)/asctime_r.Plo
+ -rm -f $(DEPDIR)/asprintf.Plo
+ -rm -f $(DEPDIR)/ctime_r.Plo
+ -rm -f $(DEPDIR)/dprintf.Plo
+ -rm -f $(DEPDIR)/fmtcheck.Plo
+ -rm -f $(DEPDIR)/getline.Plo
+ -rm -f $(DEPDIR)/getopt_long.Plo
+ -rm -f $(DEPDIR)/gmtime_r.Plo
+ -rm -f $(DEPDIR)/localtime_r.Plo
+ -rm -f $(DEPDIR)/pread.Plo
+ -rm -f $(DEPDIR)/strcasestr.Plo
+ -rm -f $(DEPDIR)/strlcat.Plo
+ -rm -f $(DEPDIR)/strlcpy.Plo
+ -rm -f $(DEPDIR)/vasprintf.Plo
+ -rm -f ./$(DEPDIR)/apprentice.Plo
+ -rm -f ./$(DEPDIR)/apptype.Plo
+ -rm -f ./$(DEPDIR)/ascmagic.Plo
+ -rm -f ./$(DEPDIR)/buffer.Plo
+ -rm -f ./$(DEPDIR)/cdf.Plo
+ -rm -f ./$(DEPDIR)/cdf_time.Plo
+ -rm -f ./$(DEPDIR)/compress.Plo
+ -rm -f ./$(DEPDIR)/der.Plo
+ -rm -f ./$(DEPDIR)/encoding.Plo
+ -rm -f ./$(DEPDIR)/file.Po
+ -rm -f ./$(DEPDIR)/fsmagic.Plo
+ -rm -f ./$(DEPDIR)/funcs.Plo
+ -rm -f ./$(DEPDIR)/is_csv.Plo
+ -rm -f ./$(DEPDIR)/is_json.Plo
+ -rm -f ./$(DEPDIR)/is_tar.Plo
+ -rm -f ./$(DEPDIR)/magic.Plo
+ -rm -f ./$(DEPDIR)/print.Plo
+ -rm -f ./$(DEPDIR)/readcdf.Plo
+ -rm -f ./$(DEPDIR)/readelf.Plo
+ -rm -f ./$(DEPDIR)/seccomp.Po
+ -rm -f ./$(DEPDIR)/softmagic.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-nodist_includeHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f $(DEPDIR)/asctime_r.Plo
+ -rm -f $(DEPDIR)/asprintf.Plo
+ -rm -f $(DEPDIR)/ctime_r.Plo
+ -rm -f $(DEPDIR)/dprintf.Plo
+ -rm -f $(DEPDIR)/fmtcheck.Plo
+ -rm -f $(DEPDIR)/getline.Plo
+ -rm -f $(DEPDIR)/getopt_long.Plo
+ -rm -f $(DEPDIR)/gmtime_r.Plo
+ -rm -f $(DEPDIR)/localtime_r.Plo
+ -rm -f $(DEPDIR)/pread.Plo
+ -rm -f $(DEPDIR)/strcasestr.Plo
+ -rm -f $(DEPDIR)/strlcat.Plo
+ -rm -f $(DEPDIR)/strlcpy.Plo
+ -rm -f $(DEPDIR)/vasprintf.Plo
+ -rm -f ./$(DEPDIR)/apprentice.Plo
+ -rm -f ./$(DEPDIR)/apptype.Plo
+ -rm -f ./$(DEPDIR)/ascmagic.Plo
+ -rm -f ./$(DEPDIR)/buffer.Plo
+ -rm -f ./$(DEPDIR)/cdf.Plo
+ -rm -f ./$(DEPDIR)/cdf_time.Plo
+ -rm -f ./$(DEPDIR)/compress.Plo
+ -rm -f ./$(DEPDIR)/der.Plo
+ -rm -f ./$(DEPDIR)/encoding.Plo
+ -rm -f ./$(DEPDIR)/file.Po
+ -rm -f ./$(DEPDIR)/fsmagic.Plo
+ -rm -f ./$(DEPDIR)/funcs.Plo
+ -rm -f ./$(DEPDIR)/is_csv.Plo
+ -rm -f ./$(DEPDIR)/is_json.Plo
+ -rm -f ./$(DEPDIR)/is_tar.Plo
+ -rm -f ./$(DEPDIR)/magic.Plo
+ -rm -f ./$(DEPDIR)/print.Plo
+ -rm -f ./$(DEPDIR)/readcdf.Plo
+ -rm -f ./$(DEPDIR)/readelf.Plo
+ -rm -f ./$(DEPDIR)/seccomp.Po
+ -rm -f ./$(DEPDIR)/softmagic.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-libLTLIBRARIES \
+ uninstall-nodist_includeHEADERS
+
+.MAKE: all check install install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES install-man \
+ install-nodist_includeHEADERS install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-libLTLIBRARIES uninstall-nodist_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+magic.h: ${HDR}
+ sed -e "s/X.YY/$$(echo @VERSION@ | tr -d .)/" < ${HDR} > $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/apprentice.c b/src/apprentice.c
new file mode 100644
index 0000000..db21787
--- /dev/null
+++ b/src/apprentice.c
@@ -0,0 +1,3728 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * apprentice - make one pass through /etc/magic, learning its secrets.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: apprentice.c,v 1.339 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <fcntl.h>
+#ifdef QUICK
+#include <sys/mman.h>
+#endif
+#include <dirent.h>
+#include <limits.h>
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifdef HAVE_SYS_BSWAP_H
+#include <sys/bswap.h>
+#endif
+
+
+#define EATAB {while (isascii(CAST(unsigned char, *l)) && \
+ isspace(CAST(unsigned char, *l))) ++l;}
+#define LOWCASE(l) (isupper(CAST(unsigned char, l)) ? \
+ tolower(CAST(unsigned char, l)) : (l))
+/*
+ * Work around a bug in headers on Digital Unix.
+ * At least confirmed for: OSF1 V4.0 878
+ */
+#if defined(__osf__) && defined(__DECC)
+#ifdef MAP_FAILED
+#undef MAP_FAILED
+#endif
+#endif
+
+#ifndef MAP_FAILED
+#define MAP_FAILED (void *) -1
+#endif
+
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+
+#define ALLOC_CHUNK CAST(size_t, 10)
+#define ALLOC_INCR CAST(size_t, 200)
+
+#define MAP_TYPE_USER 0
+#define MAP_TYPE_MALLOC 1
+#define MAP_TYPE_MMAP 2
+
+struct magic_entry {
+ struct magic *mp;
+ uint32_t cont_count;
+ uint32_t max_count;
+};
+
+struct magic_entry_set {
+ struct magic_entry *me;
+ uint32_t count;
+ uint32_t max;
+};
+
+struct magic_map {
+ void *p;
+ size_t len;
+ int type;
+ struct magic *magic[MAGIC_SETS];
+ uint32_t nmagic[MAGIC_SETS];
+};
+
+int file_formats[FILE_NAMES_SIZE];
+const size_t file_nformats = FILE_NAMES_SIZE;
+const char *file_names[FILE_NAMES_SIZE];
+const size_t file_nnames = FILE_NAMES_SIZE;
+
+file_private int getvalue(struct magic_set *ms, struct magic *, const char **, int);
+file_private int hextoint(int);
+file_private const char *getstr(struct magic_set *, struct magic *, const char *,
+ int);
+file_private int parse(struct magic_set *, struct magic_entry *, const char *,
+ size_t, int);
+file_private void eatsize(const char **);
+file_private int apprentice_1(struct magic_set *, const char *, int);
+file_private ssize_t apprentice_magic_strength_1(const struct magic *);
+file_private size_t apprentice_magic_strength(const struct magic *, size_t);
+file_private int apprentice_sort(const void *, const void *);
+file_private void apprentice_list(struct mlist *, int );
+file_private struct magic_map *apprentice_load(struct magic_set *,
+ const char *, int);
+file_private struct mlist *mlist_alloc(void);
+file_private void mlist_free_all(struct magic_set *);
+file_private void mlist_free(struct mlist *);
+file_private void byteswap(struct magic *, uint32_t);
+file_private void bs1(struct magic *);
+
+#if defined(HAVE_BYTESWAP_H)
+#define swap2(x) bswap_16(x)
+#define swap4(x) bswap_32(x)
+#define swap8(x) bswap_64(x)
+#elif defined(HAVE_SYS_BSWAP_H)
+#define swap2(x) bswap16(x)
+#define swap4(x) bswap32(x)
+#define swap8(x) bswap64(x)
+#else
+file_private uint16_t swap2(uint16_t);
+file_private uint32_t swap4(uint32_t);
+file_private uint64_t swap8(uint64_t);
+#endif
+
+file_private char *mkdbname(struct magic_set *, const char *, int);
+file_private struct magic_map *apprentice_buf(struct magic_set *, struct magic *,
+ size_t);
+file_private struct magic_map *apprentice_map(struct magic_set *, const char *);
+file_private int check_buffer(struct magic_set *, struct magic_map *, const char *);
+file_private void apprentice_unmap(struct magic_map *);
+file_private int apprentice_compile(struct magic_set *, struct magic_map *,
+ const char *);
+file_private int check_format_type(const char *, int, const char **);
+file_private int check_format(struct magic_set *, struct magic *);
+file_private int get_op(char);
+file_private int parse_mime(struct magic_set *, struct magic_entry *, const char *,
+ size_t);
+file_private int parse_strength(struct magic_set *, struct magic_entry *,
+ const char *, size_t);
+file_private int parse_apple(struct magic_set *, struct magic_entry *, const char *,
+ size_t);
+file_private int parse_ext(struct magic_set *, struct magic_entry *, const char *,
+ size_t);
+
+
+file_private size_t magicsize = sizeof(struct magic);
+
+file_private const char usg_hdr[] = "cont\toffset\ttype\topcode\tmask\tvalue\tdesc";
+
+file_private struct {
+ const char *name;
+ size_t len;
+ int (*fun)(struct magic_set *, struct magic_entry *, const char *,
+ size_t);
+} bang[] = {
+#define DECLARE_FIELD(name) { # name, sizeof(# name) - 1, parse_ ## name }
+ DECLARE_FIELD(mime),
+ DECLARE_FIELD(apple),
+ DECLARE_FIELD(ext),
+ DECLARE_FIELD(strength),
+#undef DECLARE_FIELD
+ { NULL, 0, NULL }
+};
+
+#ifdef COMPILE_ONLY
+
+int main(int, char *[]);
+
+int
+main(int argc, char *argv[])
+{
+ int ret;
+ struct magic_set *ms;
+ char *progname;
+
+ if ((progname = strrchr(argv[0], '/')) != NULL)
+ progname++;
+ else
+ progname = argv[0];
+
+ if (argc != 2) {
+ (void)fprintf(stderr, "Usage: %s file\n", progname);
+ return 1;
+ }
+
+ if ((ms = magic_open(MAGIC_CHECK)) == NULL) {
+ (void)fprintf(stderr, "%s: %s\n", progname, strerror(errno));
+ return 1;
+ }
+ ret = magic_compile(ms, argv[1]) == -1 ? 1 : 0;
+ if (ret == 1)
+ (void)fprintf(stderr, "%s: %s\n", progname, magic_error(ms));
+ magic_close(ms);
+ return ret;
+}
+#endif /* COMPILE_ONLY */
+
+struct type_tbl_s {
+ const char name[16];
+ const size_t len;
+ const int type;
+ const int format;
+};
+
+/*
+ * XXX - the actual Single UNIX Specification says that "long" means "long",
+ * as in the C data type, but we treat it as meaning "4-byte integer".
+ * Given that the OS X version of file 5.04 did the same, I guess that passes
+ * the actual test; having "long" be dependent on how big a "long" is on
+ * the machine running "file" is silly.
+ */
+static const struct type_tbl_s type_tbl[] = {
+# define XX(s) s, (sizeof(s) - 1)
+# define XX_NULL "", 0
+ { XX("invalid"), FILE_INVALID, FILE_FMT_NONE },
+ { XX("byte"), FILE_BYTE, FILE_FMT_NUM },
+ { XX("short"), FILE_SHORT, FILE_FMT_NUM },
+ { XX("default"), FILE_DEFAULT, FILE_FMT_NONE },
+ { XX("long"), FILE_LONG, FILE_FMT_NUM },
+ { XX("string"), FILE_STRING, FILE_FMT_STR },
+ { XX("date"), FILE_DATE, FILE_FMT_STR },
+ { XX("beshort"), FILE_BESHORT, FILE_FMT_NUM },
+ { XX("belong"), FILE_BELONG, FILE_FMT_NUM },
+ { XX("bedate"), FILE_BEDATE, FILE_FMT_STR },
+ { XX("leshort"), FILE_LESHORT, FILE_FMT_NUM },
+ { XX("lelong"), FILE_LELONG, FILE_FMT_NUM },
+ { XX("ledate"), FILE_LEDATE, FILE_FMT_STR },
+ { XX("pstring"), FILE_PSTRING, FILE_FMT_STR },
+ { XX("ldate"), FILE_LDATE, FILE_FMT_STR },
+ { XX("beldate"), FILE_BELDATE, FILE_FMT_STR },
+ { XX("leldate"), FILE_LELDATE, FILE_FMT_STR },
+ { XX("regex"), FILE_REGEX, FILE_FMT_STR },
+ { XX("bestring16"), FILE_BESTRING16, FILE_FMT_STR },
+ { XX("lestring16"), FILE_LESTRING16, FILE_FMT_STR },
+ { XX("search"), FILE_SEARCH, FILE_FMT_STR },
+ { XX("medate"), FILE_MEDATE, FILE_FMT_STR },
+ { XX("meldate"), FILE_MELDATE, FILE_FMT_STR },
+ { XX("melong"), FILE_MELONG, FILE_FMT_NUM },
+ { XX("quad"), FILE_QUAD, FILE_FMT_QUAD },
+ { XX("lequad"), FILE_LEQUAD, FILE_FMT_QUAD },
+ { XX("bequad"), FILE_BEQUAD, FILE_FMT_QUAD },
+ { XX("qdate"), FILE_QDATE, FILE_FMT_STR },
+ { XX("leqdate"), FILE_LEQDATE, FILE_FMT_STR },
+ { XX("beqdate"), FILE_BEQDATE, FILE_FMT_STR },
+ { XX("qldate"), FILE_QLDATE, FILE_FMT_STR },
+ { XX("leqldate"), FILE_LEQLDATE, FILE_FMT_STR },
+ { XX("beqldate"), FILE_BEQLDATE, FILE_FMT_STR },
+ { XX("float"), FILE_FLOAT, FILE_FMT_FLOAT },
+ { XX("befloat"), FILE_BEFLOAT, FILE_FMT_FLOAT },
+ { XX("lefloat"), FILE_LEFLOAT, FILE_FMT_FLOAT },
+ { XX("double"), FILE_DOUBLE, FILE_FMT_DOUBLE },
+ { XX("bedouble"), FILE_BEDOUBLE, FILE_FMT_DOUBLE },
+ { XX("ledouble"), FILE_LEDOUBLE, FILE_FMT_DOUBLE },
+ { XX("leid3"), FILE_LEID3, FILE_FMT_NUM },
+ { XX("beid3"), FILE_BEID3, FILE_FMT_NUM },
+ { XX("indirect"), FILE_INDIRECT, FILE_FMT_NUM },
+ { XX("qwdate"), FILE_QWDATE, FILE_FMT_STR },
+ { XX("leqwdate"), FILE_LEQWDATE, FILE_FMT_STR },
+ { XX("beqwdate"), FILE_BEQWDATE, FILE_FMT_STR },
+ { XX("name"), FILE_NAME, FILE_FMT_NONE },
+ { XX("use"), FILE_USE, FILE_FMT_NONE },
+ { XX("clear"), FILE_CLEAR, FILE_FMT_NONE },
+ { XX("der"), FILE_DER, FILE_FMT_STR },
+ { XX("guid"), FILE_GUID, FILE_FMT_STR },
+ { XX("offset"), FILE_OFFSET, FILE_FMT_QUAD },
+ { XX("bevarint"), FILE_BEVARINT, FILE_FMT_STR },
+ { XX("levarint"), FILE_LEVARINT, FILE_FMT_STR },
+ { XX("msdosdate"), FILE_MSDOSDATE, FILE_FMT_STR },
+ { XX("lemsdosdate"), FILE_LEMSDOSDATE, FILE_FMT_STR },
+ { XX("bemsdosdate"), FILE_BEMSDOSDATE, FILE_FMT_STR },
+ { XX("msdostime"), FILE_MSDOSTIME, FILE_FMT_STR },
+ { XX("lemsdostime"), FILE_LEMSDOSTIME, FILE_FMT_STR },
+ { XX("bemsdostime"), FILE_BEMSDOSTIME, FILE_FMT_STR },
+ { XX("octal"), FILE_OCTAL, FILE_FMT_STR },
+ { XX_NULL, FILE_INVALID, FILE_FMT_NONE },
+};
+
+/*
+ * These are not types, and cannot be preceded by "u" to make them
+ * unsigned.
+ */
+static const struct type_tbl_s special_tbl[] = {
+ { XX("der"), FILE_DER, FILE_FMT_STR },
+ { XX("name"), FILE_NAME, FILE_FMT_STR },
+ { XX("use"), FILE_USE, FILE_FMT_STR },
+ { XX("octal"), FILE_OCTAL, FILE_FMT_STR },
+ { XX_NULL, FILE_INVALID, FILE_FMT_NONE },
+};
+# undef XX
+# undef XX_NULL
+
+file_private int
+get_type(const struct type_tbl_s *tbl, const char *l, const char **t)
+{
+ const struct type_tbl_s *p;
+
+ for (p = tbl; p->len; p++) {
+ if (strncmp(l, p->name, p->len) == 0) {
+ if (t)
+ *t = l + p->len;
+ break;
+ }
+ }
+ return p->type;
+}
+
+file_private off_t
+maxoff_t(void) {
+ if (/*CONSTCOND*/sizeof(off_t) == sizeof(int))
+ return CAST(off_t, INT_MAX);
+ if (/*CONSTCOND*/sizeof(off_t) == sizeof(long))
+ return CAST(off_t, LONG_MAX);
+ return 0x7fffffff;
+}
+
+file_private int
+get_standard_integer_type(const char *l, const char **t)
+{
+ int type;
+
+ if (isalpha(CAST(unsigned char, l[1]))) {
+ switch (l[1]) {
+ case 'C':
+ /* "dC" and "uC" */
+ type = FILE_BYTE;
+ break;
+ case 'S':
+ /* "dS" and "uS" */
+ type = FILE_SHORT;
+ break;
+ case 'I':
+ case 'L':
+ /*
+ * "dI", "dL", "uI", and "uL".
+ *
+ * XXX - the actual Single UNIX Specification says
+ * that "L" means "long", as in the C data type,
+ * but we treat it as meaning "4-byte integer".
+ * Given that the OS X version of file 5.04 did
+ * the same, I guess that passes the actual SUS
+ * validation suite; having "dL" be dependent on
+ * how big a "long" is on the machine running
+ * "file" is silly.
+ */
+ type = FILE_LONG;
+ break;
+ case 'Q':
+ /* "dQ" and "uQ" */
+ type = FILE_QUAD;
+ break;
+ default:
+ /* "d{anything else}", "u{anything else}" */
+ return FILE_INVALID;
+ }
+ l += 2;
+ } else if (isdigit(CAST(unsigned char, l[1]))) {
+ /*
+ * "d{num}" and "u{num}"; we only support {num} values
+ * of 1, 2, 4, and 8 - the Single UNIX Specification
+ * doesn't say anything about whether arbitrary
+ * values should be supported, but both the Solaris 10
+ * and OS X Mountain Lion versions of file passed the
+ * Single UNIX Specification validation suite, and
+ * neither of them support values bigger than 8 or
+ * non-power-of-2 values.
+ */
+ if (isdigit(CAST(unsigned char, l[2]))) {
+ /* Multi-digit, so > 9 */
+ return FILE_INVALID;
+ }
+ switch (l[1]) {
+ case '1':
+ type = FILE_BYTE;
+ break;
+ case '2':
+ type = FILE_SHORT;
+ break;
+ case '4':
+ type = FILE_LONG;
+ break;
+ case '8':
+ type = FILE_QUAD;
+ break;
+ default:
+ /* XXX - what about 3, 5, 6, or 7? */
+ return FILE_INVALID;
+ }
+ l += 2;
+ } else {
+ /*
+ * "d" or "u" by itself.
+ */
+ type = FILE_LONG;
+ ++l;
+ }
+ if (t)
+ *t = l;
+ return type;
+}
+
+file_private void
+init_file_tables(void)
+{
+ static int done = 0;
+ const struct type_tbl_s *p;
+
+ if (done)
+ return;
+ done++;
+
+ for (p = type_tbl; p->len; p++) {
+ assert(p->type < FILE_NAMES_SIZE);
+ file_names[p->type] = p->name;
+ file_formats[p->type] = p->format;
+ }
+ assert(p - type_tbl == FILE_NAMES_SIZE);
+}
+
+file_private int
+add_mlist(struct mlist *mlp, struct magic_map *map, size_t idx)
+{
+ struct mlist *ml;
+
+ mlp->map = NULL;
+ if ((ml = CAST(struct mlist *, malloc(sizeof(*ml)))) == NULL)
+ return -1;
+
+ ml->map = idx == 0 ? map : NULL;
+ ml->magic = map->magic[idx];
+ ml->nmagic = map->nmagic[idx];
+ if (ml->nmagic) {
+ ml->magic_rxcomp = CAST(file_regex_t **,
+ calloc(ml->nmagic, sizeof(*ml->magic_rxcomp)));
+ if (ml->magic_rxcomp == NULL) {
+ free(ml);
+ return -1;
+ }
+ } else
+ ml->magic_rxcomp = NULL;
+ mlp->prev->next = ml;
+ ml->prev = mlp->prev;
+ ml->next = mlp;
+ mlp->prev = ml;
+ return 0;
+}
+
+/*
+ * Handle one file or directory.
+ */
+file_private int
+apprentice_1(struct magic_set *ms, const char *fn, int action)
+{
+ struct magic_map *map;
+#ifndef COMPILE_ONLY
+ size_t i;
+#endif
+
+ if (magicsize != FILE_MAGICSIZE) {
+ file_error(ms, 0, "magic element size %lu != %lu",
+ CAST(unsigned long, sizeof(*map->magic[0])),
+ CAST(unsigned long, FILE_MAGICSIZE));
+ return -1;
+ }
+
+ if (action == FILE_COMPILE) {
+ map = apprentice_load(ms, fn, action);
+ if (map == NULL)
+ return -1;
+ return apprentice_compile(ms, map, fn);
+ }
+
+#ifndef COMPILE_ONLY
+ map = apprentice_map(ms, fn);
+ if (map == NULL) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(NULL, "using regular magic file `%s'", fn);
+ map = apprentice_load(ms, fn, action);
+ if (map == NULL)
+ return -1;
+ }
+
+ for (i = 0; i < MAGIC_SETS; i++) {
+ if (add_mlist(ms->mlist[i], map, i) == -1) {
+ /* failed to add to any list, free explicitly */
+ if (i == 0)
+ apprentice_unmap(map);
+ else
+ mlist_free_all(ms);
+ file_oomem(ms, sizeof(*ms->mlist[0]));
+ return -1;
+ }
+ }
+
+ if (action == FILE_LIST) {
+ for (i = 0; i < MAGIC_SETS; i++) {
+ printf("Set %" SIZE_T_FORMAT "u:\nBinary patterns:\n",
+ i);
+ apprentice_list(ms->mlist[i], BINTEST);
+ printf("Text patterns:\n");
+ apprentice_list(ms->mlist[i], TEXTTEST);
+ }
+ }
+ return 0;
+#else
+ return 0;
+#endif /* COMPILE_ONLY */
+}
+
+file_protected void
+file_ms_free(struct magic_set *ms)
+{
+ size_t i;
+ if (ms == NULL)
+ return;
+ for (i = 0; i < MAGIC_SETS; i++)
+ mlist_free(ms->mlist[i]);
+ free(ms->o.pbuf);
+ free(ms->o.buf);
+ free(ms->c.li);
+#ifdef USE_C_LOCALE
+ freelocale(ms->c_lc_ctype);
+#endif
+ free(ms);
+}
+
+file_protected struct magic_set *
+file_ms_alloc(int flags)
+{
+ struct magic_set *ms;
+ size_t i, len;
+
+ if ((ms = CAST(struct magic_set *, calloc(CAST(size_t, 1u),
+ sizeof(*ms)))) == NULL)
+ return NULL;
+
+ if (magic_setflags(ms, flags) == -1) {
+ errno = EINVAL;
+ goto free;
+ }
+
+ ms->o.buf = ms->o.pbuf = NULL;
+ ms->o.blen = 0;
+ len = (ms->c.len = 10) * sizeof(*ms->c.li);
+
+ if ((ms->c.li = CAST(struct level_info *, malloc(len))) == NULL)
+ goto free;
+
+ ms->event_flags = 0;
+ ms->error = -1;
+ for (i = 0; i < MAGIC_SETS; i++)
+ ms->mlist[i] = NULL;
+ ms->file = "unknown";
+ ms->line = 0;
+ ms->indir_max = FILE_INDIR_MAX;
+ ms->name_max = FILE_NAME_MAX;
+ ms->elf_shnum_max = FILE_ELF_SHNUM_MAX;
+ ms->elf_phnum_max = FILE_ELF_PHNUM_MAX;
+ ms->elf_notes_max = FILE_ELF_NOTES_MAX;
+ ms->regex_max = FILE_REGEX_MAX;
+ ms->bytes_max = FILE_BYTES_MAX;
+ ms->encoding_max = FILE_ENCODING_MAX;
+#ifdef USE_C_LOCALE
+ ms->c_lc_ctype = newlocale(LC_CTYPE_MASK, "C", 0);
+ assert(ms->c_lc_ctype != NULL);
+#endif
+ return ms;
+free:
+ free(ms);
+ return NULL;
+}
+
+file_private void
+apprentice_unmap(struct magic_map *map)
+{
+ size_t i;
+ char *p;
+ if (map == NULL)
+ return;
+
+ switch (map->type) {
+ case MAP_TYPE_USER:
+ break;
+ case MAP_TYPE_MALLOC:
+ p = CAST(char *, map->p);
+ for (i = 0; i < MAGIC_SETS; i++) {
+ char *b = RCAST(char *, map->magic[i]);
+ if (p != NULL && b >= p && b <= p + map->len)
+ continue;
+ free(b);
+ }
+ free(p);
+ break;
+#ifdef QUICK
+ case MAP_TYPE_MMAP:
+ if (map->p && map->p != MAP_FAILED)
+ (void)munmap(map->p, map->len);
+ break;
+#endif
+ default:
+ fprintf(stderr, "Bad map type %d", map->type);
+ abort();
+ }
+ free(map);
+}
+
+file_private struct mlist *
+mlist_alloc(void)
+{
+ struct mlist *mlist;
+ if ((mlist = CAST(struct mlist *, calloc(1, sizeof(*mlist)))) == NULL) {
+ return NULL;
+ }
+ mlist->next = mlist->prev = mlist;
+ return mlist;
+}
+
+file_private void
+mlist_free_all(struct magic_set *ms)
+{
+ size_t i;
+
+ for (i = 0; i < MAGIC_SETS; i++) {
+ mlist_free(ms->mlist[i]);
+ ms->mlist[i] = NULL;
+ }
+}
+
+file_private void
+mlist_free_one(struct mlist *ml)
+{
+ size_t i;
+
+ if (ml->map)
+ apprentice_unmap(CAST(struct magic_map *, ml->map));
+
+ for (i = 0; i < ml->nmagic; ++i) {
+ if (ml->magic_rxcomp[i]) {
+ file_regfree(ml->magic_rxcomp[i]);
+ free(ml->magic_rxcomp[i]);
+ ml->magic_rxcomp[i] = NULL;
+ }
+ }
+ free(ml->magic_rxcomp);
+ ml->magic_rxcomp = NULL;
+ free(ml);
+}
+
+file_private void
+mlist_free(struct mlist *mlist)
+{
+ struct mlist *ml, *next;
+
+ if (mlist == NULL)
+ return;
+
+ for (ml = mlist->next; ml != mlist;) {
+ next = ml->next;
+ mlist_free_one(ml);
+ ml = next;
+ }
+ mlist_free_one(mlist);
+}
+
+#ifndef COMPILE_ONLY
+/* void **bufs: an array of compiled magic files */
+file_protected int
+buffer_apprentice(struct magic_set *ms, struct magic **bufs,
+ size_t *sizes, size_t nbufs)
+{
+ size_t i, j;
+ struct magic_map *map;
+
+ if (nbufs == 0)
+ return -1;
+
+ (void)file_reset(ms, 0);
+
+ init_file_tables();
+
+ for (i = 0; i < MAGIC_SETS; i++) {
+ mlist_free(ms->mlist[i]);
+ if ((ms->mlist[i] = mlist_alloc()) == NULL) {
+ file_oomem(ms, sizeof(*ms->mlist[0]));
+ goto fail;
+ }
+ }
+
+ for (i = 0; i < nbufs; i++) {
+ map = apprentice_buf(ms, bufs[i], sizes[i]);
+ if (map == NULL)
+ goto fail;
+
+ for (j = 0; j < MAGIC_SETS; j++) {
+ if (add_mlist(ms->mlist[j], map, j) == -1) {
+ file_oomem(ms, sizeof(*ms->mlist[0]));
+ goto fail;
+ }
+ }
+ }
+
+ return 0;
+fail:
+ mlist_free_all(ms);
+ return -1;
+}
+#endif
+
+/* const char *fn: list of magic files and directories */
+file_protected int
+file_apprentice(struct magic_set *ms, const char *fn, int action)
+{
+ char *p, *mfn;
+ int fileerr, errs = -1;
+ size_t i, j;
+
+ (void)file_reset(ms, 0);
+
+ if ((fn = magic_getpath(fn, action)) == NULL)
+ return -1;
+
+ init_file_tables();
+
+ if ((mfn = strdup(fn)) == NULL) {
+ file_oomem(ms, strlen(fn));
+ return -1;
+ }
+
+ for (i = 0; i < MAGIC_SETS; i++) {
+ mlist_free(ms->mlist[i]);
+ if ((ms->mlist[i] = mlist_alloc()) == NULL) {
+ file_oomem(ms, sizeof(*ms->mlist[0]));
+ for (j = 0; j < i; j++) {
+ mlist_free(ms->mlist[j]);
+ ms->mlist[j] = NULL;
+ }
+ free(mfn);
+ return -1;
+ }
+ }
+ fn = mfn;
+
+ while (fn) {
+ p = CCAST(char *, strchr(fn, PATHSEP));
+ if (p)
+ *p++ = '\0';
+ if (*fn == '\0')
+ break;
+ fileerr = apprentice_1(ms, fn, action);
+ errs = MAX(errs, fileerr);
+ fn = p;
+ }
+
+ free(mfn);
+
+ if (errs == -1) {
+ for (i = 0; i < MAGIC_SETS; i++) {
+ mlist_free(ms->mlist[i]);
+ ms->mlist[i] = NULL;
+ }
+ file_error(ms, 0, "could not find any valid magic files!");
+ return -1;
+ }
+
+#if 0
+ /*
+ * Always leave the database loaded
+ */
+ if (action == FILE_LOAD)
+ return 0;
+
+ for (i = 0; i < MAGIC_SETS; i++) {
+ mlist_free(ms->mlist[i]);
+ ms->mlist[i] = NULL;
+ }
+#endif
+
+ switch (action) {
+ case FILE_LOAD:
+ case FILE_COMPILE:
+ case FILE_CHECK:
+ case FILE_LIST:
+ return 0;
+ default:
+ file_error(ms, 0, "Invalid action %d", action);
+ return -1;
+ }
+}
+
+/*
+ * Compute the real length of a magic expression, for the purposes
+ * of determining how "strong" a magic expression is (approximating
+ * how specific its matches are):
+ * - magic characters count 0 unless escaped.
+ * - [] expressions count 1
+ * - {} expressions count 0
+ * - regular characters or escaped magic characters count 1
+ * - 0 length expressions count as one
+ */
+file_private size_t
+nonmagic(const char *str)
+{
+ const char *p;
+ size_t rv = 0;
+
+ for (p = str; *p; p++)
+ switch (*p) {
+ case '\\': /* Escaped anything counts 1 */
+ if (!*++p)
+ p--;
+ rv++;
+ continue;
+ case '?': /* Magic characters count 0 */
+ case '*':
+ case '.':
+ case '+':
+ case '^':
+ case '$':
+ continue;
+ case '[': /* Bracketed expressions count 1 the ']' */
+ while (*p && *p != ']')
+ p++;
+ p--;
+ continue;
+ case '{': /* Braced expressions count 0 */
+ while (*p && *p != '}')
+ p++;
+ if (!*p)
+ p--;
+ continue;
+ default: /* Anything else counts 1 */
+ rv++;
+ continue;
+ }
+
+ return rv == 0 ? 1 : rv; /* Return at least 1 */
+}
+
+
+file_private size_t
+typesize(int type)
+{
+ switch (type) {
+ case FILE_BYTE:
+ return 1;
+
+ case FILE_SHORT:
+ case FILE_LESHORT:
+ case FILE_BESHORT:
+ case FILE_MSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ return 2;
+
+ case FILE_LONG:
+ case FILE_LELONG:
+ case FILE_BELONG:
+ case FILE_MELONG:
+ return 4;
+
+ case FILE_DATE:
+ case FILE_LEDATE:
+ case FILE_BEDATE:
+ case FILE_MEDATE:
+ case FILE_LDATE:
+ case FILE_LELDATE:
+ case FILE_BELDATE:
+ case FILE_MELDATE:
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ case FILE_BEID3:
+ case FILE_LEID3:
+ return 4;
+
+ case FILE_QUAD:
+ case FILE_BEQUAD:
+ case FILE_LEQUAD:
+ case FILE_QDATE:
+ case FILE_LEQDATE:
+ case FILE_BEQDATE:
+ case FILE_QLDATE:
+ case FILE_LEQLDATE:
+ case FILE_BEQLDATE:
+ case FILE_QWDATE:
+ case FILE_LEQWDATE:
+ case FILE_BEQWDATE:
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ case FILE_OFFSET:
+ case FILE_BEVARINT:
+ case FILE_LEVARINT:
+ return 8;
+
+ case FILE_GUID:
+ return 16;
+
+ default:
+ return FILE_BADSIZE;
+ }
+}
+
+/*
+ * Get weight of this magic entry, for sorting purposes.
+ */
+file_private ssize_t
+apprentice_magic_strength_1(const struct magic *m)
+{
+#define MULT 10U
+ size_t ts, v;
+ ssize_t val = 2 * MULT; /* baseline strength */
+
+ switch (m->type) {
+ case FILE_DEFAULT: /* make sure this sorts last */
+ if (m->factor_op != FILE_FACTOR_OP_NONE) {
+ file_magwarn(NULL, "Usupported factor_op in default %d",
+ m->factor_op);
+ }
+ return 0;
+
+ case FILE_BYTE:
+ case FILE_SHORT:
+ case FILE_LESHORT:
+ case FILE_BESHORT:
+ case FILE_LONG:
+ case FILE_LELONG:
+ case FILE_BELONG:
+ case FILE_MELONG:
+ case FILE_DATE:
+ case FILE_LEDATE:
+ case FILE_BEDATE:
+ case FILE_MEDATE:
+ case FILE_LDATE:
+ case FILE_LELDATE:
+ case FILE_BELDATE:
+ case FILE_MELDATE:
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ case FILE_QUAD:
+ case FILE_BEQUAD:
+ case FILE_LEQUAD:
+ case FILE_QDATE:
+ case FILE_LEQDATE:
+ case FILE_BEQDATE:
+ case FILE_QLDATE:
+ case FILE_LEQLDATE:
+ case FILE_BEQLDATE:
+ case FILE_QWDATE:
+ case FILE_LEQWDATE:
+ case FILE_BEQWDATE:
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ case FILE_BEVARINT:
+ case FILE_LEVARINT:
+ case FILE_GUID:
+ case FILE_BEID3:
+ case FILE_LEID3:
+ case FILE_OFFSET:
+ case FILE_MSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ ts = typesize(m->type);
+ if (ts == FILE_BADSIZE) {
+ (void)fprintf(stderr, "Bad size for type %d\n",
+ m->type);
+ abort();
+ }
+ val += ts * MULT;
+ break;
+
+ case FILE_PSTRING:
+ case FILE_STRING:
+ case FILE_OCTAL:
+ val += m->vallen * MULT;
+ break;
+
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ val += m->vallen * MULT / 2;
+ break;
+
+ case FILE_SEARCH:
+ if (m->vallen == 0)
+ break;
+ val += m->vallen * MAX(MULT / m->vallen, 1);
+ break;
+
+ case FILE_REGEX:
+ v = nonmagic(m->value.s);
+ val += v * MAX(MULT / v, 1);
+ break;
+
+ case FILE_INDIRECT:
+ case FILE_NAME:
+ case FILE_USE:
+ case FILE_CLEAR:
+ break;
+
+ case FILE_DER:
+ val += MULT;
+ break;
+
+ default:
+ (void)fprintf(stderr, "Bad type %d\n", m->type);
+ abort();
+ }
+
+ switch (m->reln) {
+ case 'x': /* matches anything penalize */
+ case '!': /* matches almost anything penalize */
+ val = 0;
+ break;
+
+ case '=': /* Exact match, prefer */
+ val += MULT;
+ break;
+
+ case '>':
+ case '<': /* comparison match reduce strength */
+ val -= 2 * MULT;
+ break;
+
+ case '^':
+ case '&': /* masking bits, we could count them too */
+ val -= MULT;
+ break;
+
+ default:
+ (void)fprintf(stderr, "Bad relation %c\n", m->reln);
+ abort();
+ }
+
+ return val;
+}
+
+
+/*ARGSUSED*/
+file_private size_t
+apprentice_magic_strength(const struct magic *m,
+ size_t nmagic __attribute__((__unused__)))
+{
+ ssize_t val = apprentice_magic_strength_1(m);
+
+#ifdef notyet
+ if (m->desc[0] == '\0') {
+ size_t i;
+ /*
+ * Magic entries with no description get their continuations
+ * added
+ */
+ for (i = 1; m[i].cont_level != 0 && i < MIN(nmagic, 3); i++) {
+ ssize_t v = apprentice_magic_strength_1(&m[i]) >>
+ (i + 1);
+ val += v;
+ if (m[i].desc[0] != '\0')
+ break;
+ }
+ }
+#endif
+
+ switch (m->factor_op) {
+ case FILE_FACTOR_OP_NONE:
+ break;
+ case FILE_FACTOR_OP_PLUS:
+ val += m->factor;
+ break;
+ case FILE_FACTOR_OP_MINUS:
+ val -= m->factor;
+ break;
+ case FILE_FACTOR_OP_TIMES:
+ val *= m->factor;
+ break;
+ case FILE_FACTOR_OP_DIV:
+ val /= m->factor;
+ break;
+ default:
+ (void)fprintf(stderr, "Bad factor_op %u\n", m->factor_op);
+ abort();
+ }
+
+ if (val <= 0) /* ensure we only return 0 for FILE_DEFAULT */
+ val = 1;
+
+#ifndef notyet
+ /*
+ * Magic entries with no description get a bonus because they depend
+ * on subsequent magic entries to print something.
+ */
+ if (m->desc[0] == '\0')
+ val++;
+#endif
+
+ return val;
+}
+
+/*
+ * Sort callback for sorting entries by "strength" (basically length)
+ */
+file_private int
+apprentice_sort(const void *a, const void *b)
+{
+ const struct magic_entry *ma = CAST(const struct magic_entry *, a);
+ const struct magic_entry *mb = CAST(const struct magic_entry *, b);
+ size_t sa = apprentice_magic_strength(ma->mp, ma->cont_count);
+ size_t sb = apprentice_magic_strength(mb->mp, mb->cont_count);
+ if (sa == sb)
+ return 0;
+ else if (sa > sb)
+ return -1;
+ else
+ return 1;
+}
+
+/*
+ * Shows sorted patterns list in the order which is used for the matching
+ */
+file_private void
+apprentice_list(struct mlist *mlist, int mode)
+{
+ uint32_t magindex, descindex, mimeindex, lineindex;
+ struct mlist *ml;
+ for (ml = mlist->next; ml != mlist; ml = ml->next) {
+ for (magindex = 0; magindex < ml->nmagic; magindex++) {
+ struct magic *m = &ml->magic[magindex];
+ if ((m->flag & mode) != mode) {
+ /* Skip sub-tests */
+ while (magindex + 1 < ml->nmagic &&
+ ml->magic[magindex + 1].cont_level != 0)
+ ++magindex;
+ continue; /* Skip to next top-level test*/
+ }
+
+ /*
+ * Try to iterate over the tree until we find item with
+ * description/mimetype.
+ */
+ lineindex = descindex = mimeindex = magindex;
+ for (; magindex + 1 < ml->nmagic &&
+ ml->magic[magindex + 1].cont_level != 0;
+ magindex++) {
+ uint32_t mi = magindex + 1;
+ if (*ml->magic[descindex].desc == '\0'
+ && *ml->magic[mi].desc)
+ descindex = mi;
+ if (*ml->magic[mimeindex].mimetype == '\0'
+ && *ml->magic[mi].mimetype)
+ mimeindex = mi;
+ }
+
+ printf("Strength = %3" SIZE_T_FORMAT "u@%u: %s [%s]\n",
+ apprentice_magic_strength(m, ml->nmagic - magindex),
+ ml->magic[lineindex].lineno,
+ ml->magic[descindex].desc,
+ ml->magic[mimeindex].mimetype);
+ }
+ }
+}
+
+file_private void
+set_test_type(struct magic *mstart, struct magic *m)
+{
+ switch (m->type) {
+ case FILE_BYTE:
+ case FILE_SHORT:
+ case FILE_LONG:
+ case FILE_DATE:
+ case FILE_BESHORT:
+ case FILE_BELONG:
+ case FILE_BEDATE:
+ case FILE_LESHORT:
+ case FILE_LELONG:
+ case FILE_LEDATE:
+ case FILE_LDATE:
+ case FILE_BELDATE:
+ case FILE_LELDATE:
+ case FILE_MEDATE:
+ case FILE_MELDATE:
+ case FILE_MELONG:
+ case FILE_QUAD:
+ case FILE_LEQUAD:
+ case FILE_BEQUAD:
+ case FILE_QDATE:
+ case FILE_LEQDATE:
+ case FILE_BEQDATE:
+ case FILE_QLDATE:
+ case FILE_LEQLDATE:
+ case FILE_BEQLDATE:
+ case FILE_QWDATE:
+ case FILE_LEQWDATE:
+ case FILE_BEQWDATE:
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ case FILE_BEVARINT:
+ case FILE_LEVARINT:
+ case FILE_DER:
+ case FILE_GUID:
+ case FILE_OFFSET:
+ case FILE_MSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ case FILE_OCTAL:
+ mstart->flag |= BINTEST;
+ break;
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ /* Allow text overrides */
+ if (mstart->str_flags & STRING_TEXTTEST)
+ mstart->flag |= TEXTTEST;
+ else
+ mstart->flag |= BINTEST;
+ break;
+ case FILE_REGEX:
+ case FILE_SEARCH:
+ /* Check for override */
+ if (mstart->str_flags & STRING_BINTEST)
+ mstart->flag |= BINTEST;
+ if (mstart->str_flags & STRING_TEXTTEST)
+ mstart->flag |= TEXTTEST;
+
+ if (mstart->flag & (TEXTTEST|BINTEST))
+ break;
+
+ /* binary test if pattern is not text */
+ if (file_looks_utf8(m->value.us, CAST(size_t, m->vallen), NULL,
+ NULL) <= 0)
+ mstart->flag |= BINTEST;
+ else
+ mstart->flag |= TEXTTEST;
+ break;
+ case FILE_DEFAULT:
+ /* can't deduce anything; we shouldn't see this at the
+ top level anyway */
+ break;
+ case FILE_INVALID:
+ default:
+ /* invalid search type, but no need to complain here */
+ break;
+ }
+}
+
+file_private int
+addentry(struct magic_set *ms, struct magic_entry *me,
+ struct magic_entry_set *mset)
+{
+ size_t i = me->mp->type == FILE_NAME ? 1 : 0;
+ if (mset[i].me == NULL || mset[i].count == mset[i].max) {
+ struct magic_entry *mp;
+
+ size_t incr = mset[i].max + ALLOC_INCR;
+ if ((mp = CAST(struct magic_entry *,
+ realloc(mset[i].me, sizeof(*mp) * incr))) ==
+ NULL) {
+ file_oomem(ms, sizeof(*mp) * incr);
+ return -1;
+ }
+ (void)memset(&mp[mset[i].count], 0, sizeof(*mp) *
+ ALLOC_INCR);
+ mset[i].me = mp;
+ mset[i].max = CAST(uint32_t, incr);
+ assert(mset[i].max == incr);
+ }
+ mset[i].me[mset[i].count++] = *me;
+ memset(me, 0, sizeof(*me));
+ return 0;
+}
+
+/*
+ * Load and parse one file.
+ */
+file_private void
+load_1(struct magic_set *ms, int action, const char *fn, int *errs,
+ struct magic_entry_set *mset)
+{
+ size_t lineno = 0, llen = 0;
+ char *line = NULL;
+ ssize_t len;
+ struct magic_entry me;
+
+ FILE *f = fopen(ms->file = fn, "r");
+ if (f == NULL) {
+ if (errno != ENOENT)
+ file_error(ms, errno, "cannot read magic file `%s'",
+ fn);
+ (*errs)++;
+ return;
+ }
+
+ memset(&me, 0, sizeof(me));
+ /* read and parse this file */
+ for (ms->line = 1; (len = getline(&line, &llen, f)) != -1;
+ ms->line++) {
+ if (len == 0) /* null line, garbage, etc */
+ continue;
+ if (line[len - 1] == '\n') {
+ lineno++;
+ line[len - 1] = '\0'; /* delete newline */
+ }
+ switch (line[0]) {
+ case '\0': /* empty, do not parse */
+ case '#': /* comment, do not parse */
+ continue;
+ case '!':
+ if (line[1] == ':') {
+ size_t i;
+
+ for (i = 0; bang[i].name != NULL; i++) {
+ if (CAST(size_t, len - 2) > bang[i].len &&
+ memcmp(bang[i].name, line + 2,
+ bang[i].len) == 0)
+ break;
+ }
+ if (bang[i].name == NULL) {
+ file_error(ms, 0,
+ "Unknown !: entry `%s'", line);
+ (*errs)++;
+ continue;
+ }
+ if (me.mp == NULL) {
+ file_error(ms, 0,
+ "No current entry for :!%s type",
+ bang[i].name);
+ (*errs)++;
+ continue;
+ }
+ if ((*bang[i].fun)(ms, &me,
+ line + bang[i].len + 2,
+ len - bang[i].len - 2) != 0) {
+ (*errs)++;
+ continue;
+ }
+ continue;
+ }
+ /*FALLTHROUGH*/
+ default:
+ again:
+ switch (parse(ms, &me, line, lineno, action)) {
+ case 0:
+ continue;
+ case 1:
+ (void)addentry(ms, &me, mset);
+ goto again;
+ default:
+ (*errs)++;
+ break;
+ }
+ }
+ }
+ if (me.mp)
+ (void)addentry(ms, &me, mset);
+ free(line);
+ (void)fclose(f);
+}
+
+/*
+ * parse a file or directory of files
+ * const char *fn: name of magic file or directory
+ */
+file_private int
+cmpstrp(const void *p1, const void *p2)
+{
+ return strcmp(*RCAST(char *const *, p1), *RCAST(char *const *, p2));
+}
+
+
+file_private uint32_t
+set_text_binary(struct magic_set *ms, struct magic_entry *me, uint32_t nme,
+ uint32_t starttest)
+{
+ static const char text[] = "text";
+ static const char binary[] = "binary";
+ static const size_t len = sizeof(text);
+
+ uint32_t i = starttest;
+
+ do {
+ set_test_type(me[starttest].mp, me[i].mp);
+ if ((ms->flags & MAGIC_DEBUG) == 0)
+ continue;
+ (void)fprintf(stderr, "%s%s%s: %s\n",
+ me[i].mp->mimetype,
+ me[i].mp->mimetype[0] == '\0' ? "" : "; ",
+ me[i].mp->desc[0] ? me[i].mp->desc : "(no description)",
+ me[i].mp->flag & BINTEST ? binary : text);
+ if (me[i].mp->flag & BINTEST) {
+ char *p = strstr(me[i].mp->desc, text);
+ if (p && (p == me[i].mp->desc ||
+ isspace(CAST(unsigned char, p[-1]))) &&
+ (p + len - me[i].mp->desc == MAXstring
+ || (p[len] == '\0' ||
+ isspace(CAST(unsigned char, p[len])))))
+ (void)fprintf(stderr, "*** Possible "
+ "binary test for text type\n");
+ }
+ } while (++i < nme && me[i].mp->cont_level != 0);
+ return i;
+}
+
+file_private void
+set_last_default(struct magic_set *ms, struct magic_entry *me, uint32_t nme)
+{
+ uint32_t i;
+ for (i = 0; i < nme; i++) {
+ if (me[i].mp->cont_level == 0 &&
+ me[i].mp->type == FILE_DEFAULT) {
+ while (++i < nme)
+ if (me[i].mp->cont_level == 0)
+ break;
+ if (i != nme) {
+ /* XXX - Ugh! */
+ ms->line = me[i].mp->lineno;
+ file_magwarn(ms,
+ "level 0 \"default\" did not sort last");
+ }
+ return;
+ }
+ }
+}
+
+file_private int
+coalesce_entries(struct magic_set *ms, struct magic_entry *me, uint32_t nme,
+ struct magic **ma, uint32_t *nma)
+{
+ uint32_t i, mentrycount = 0;
+ size_t slen;
+
+ for (i = 0; i < nme; i++)
+ mentrycount += me[i].cont_count;
+
+ if (mentrycount == 0) {
+ *ma = NULL;
+ *nma = 0;
+ return 0;
+ }
+
+ slen = sizeof(**ma) * mentrycount;
+ if ((*ma = CAST(struct magic *, malloc(slen))) == NULL) {
+ file_oomem(ms, slen);
+ return -1;
+ }
+
+ mentrycount = 0;
+ for (i = 0; i < nme; i++) {
+ (void)memcpy(*ma + mentrycount, me[i].mp,
+ me[i].cont_count * sizeof(**ma));
+ mentrycount += me[i].cont_count;
+ }
+ *nma = mentrycount;
+ return 0;
+}
+
+file_private void
+magic_entry_free(struct magic_entry *me, uint32_t nme)
+{
+ uint32_t i;
+ if (me == NULL)
+ return;
+ for (i = 0; i < nme; i++)
+ free(me[i].mp);
+ free(me);
+}
+
+file_private struct magic_map *
+apprentice_load(struct magic_set *ms, const char *fn, int action)
+{
+ int errs = 0;
+ uint32_t i, j;
+ size_t files = 0, maxfiles = 0;
+ char **filearr = NULL, *mfn;
+ struct stat st;
+ struct magic_map *map;
+ struct magic_entry_set mset[MAGIC_SETS];
+ DIR *dir;
+ struct dirent *d;
+
+ memset(mset, 0, sizeof(mset));
+ ms->flags |= MAGIC_CHECK; /* Enable checks for parsed files */
+
+
+ if ((map = CAST(struct magic_map *, calloc(1, sizeof(*map)))) == NULL)
+ {
+ file_oomem(ms, sizeof(*map));
+ return NULL;
+ }
+ map->type = MAP_TYPE_MALLOC;
+
+ /* print silly verbose header for USG compat. */
+ if (action == FILE_CHECK)
+ (void)fprintf(stderr, "%s\n", usg_hdr);
+
+ /* load directory or file */
+ if (stat(fn, &st) == 0 && S_ISDIR(st.st_mode)) {
+ dir = opendir(fn);
+ if (!dir) {
+ errs++;
+ goto out;
+ }
+ while ((d = readdir(dir)) != NULL) {
+ if (d->d_name[0] == '.')
+ continue;
+ if (asprintf(&mfn, "%s/%s", fn, d->d_name) < 0) {
+ file_oomem(ms,
+ strlen(fn) + strlen(d->d_name) + 2);
+ errs++;
+ closedir(dir);
+ goto out;
+ }
+ if (stat(mfn, &st) == -1 || !S_ISREG(st.st_mode)) {
+ free(mfn);
+ continue;
+ }
+ if (files >= maxfiles) {
+ size_t mlen;
+ char **nfilearr;
+ maxfiles = (maxfiles + 1) * 2;
+ mlen = maxfiles * sizeof(*filearr);
+ if ((nfilearr = CAST(char **,
+ realloc(filearr, mlen))) == NULL) {
+ file_oomem(ms, mlen);
+ free(mfn);
+ closedir(dir);
+ errs++;
+ goto out;
+ }
+ filearr = nfilearr;
+ }
+ filearr[files++] = mfn;
+ }
+ closedir(dir);
+ if (filearr) {
+ qsort(filearr, files, sizeof(*filearr), cmpstrp);
+ for (i = 0; i < files; i++) {
+ load_1(ms, action, filearr[i], &errs, mset);
+ free(filearr[i]);
+ }
+ free(filearr);
+ filearr = NULL;
+ }
+ } else
+ load_1(ms, action, fn, &errs, mset);
+ if (errs)
+ goto out;
+
+ for (j = 0; j < MAGIC_SETS; j++) {
+ /* Set types of tests */
+ for (i = 0; i < mset[j].count; ) {
+ if (mset[j].me[i].mp->cont_level != 0) {
+ i++;
+ continue;
+ }
+ i = set_text_binary(ms, mset[j].me, mset[j].count, i);
+ }
+ if (mset[j].me)
+ qsort(mset[j].me, mset[j].count, sizeof(*mset[0].me),
+ apprentice_sort);
+
+ /*
+ * Make sure that any level 0 "default" line is last
+ * (if one exists).
+ */
+ set_last_default(ms, mset[j].me, mset[j].count);
+
+ /* coalesce per file arrays into a single one, if needed */
+ if (mset[j].count == 0)
+ continue;
+
+ if (coalesce_entries(ms, mset[j].me, mset[j].count,
+ &map->magic[j], &map->nmagic[j]) == -1) {
+ errs++;
+ goto out;
+ }
+ }
+
+out:
+ free(filearr);
+ for (j = 0; j < MAGIC_SETS; j++)
+ magic_entry_free(mset[j].me, mset[j].count);
+
+ if (errs) {
+ apprentice_unmap(map);
+ return NULL;
+ }
+ return map;
+}
+
+/*
+ * extend the sign bit if the comparison is to be signed
+ */
+file_protected uint64_t
+file_signextend(struct magic_set *ms, struct magic *m, uint64_t v)
+{
+ if (!(m->flag & UNSIGNED)) {
+ switch(m->type) {
+ /*
+ * Do not remove the casts below. They are
+ * vital. When later compared with the data,
+ * the sign extension must have happened.
+ */
+ case FILE_BYTE:
+ v = CAST(signed char, v);
+ break;
+ case FILE_SHORT:
+ case FILE_BESHORT:
+ case FILE_LESHORT:
+ v = CAST(short, v);
+ break;
+ case FILE_DATE:
+ case FILE_BEDATE:
+ case FILE_LEDATE:
+ case FILE_MEDATE:
+ case FILE_LDATE:
+ case FILE_BELDATE:
+ case FILE_LELDATE:
+ case FILE_MELDATE:
+ case FILE_LONG:
+ case FILE_BELONG:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ case FILE_MSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ v = CAST(int32_t, v);
+ break;
+ case FILE_QUAD:
+ case FILE_BEQUAD:
+ case FILE_LEQUAD:
+ case FILE_QDATE:
+ case FILE_QLDATE:
+ case FILE_QWDATE:
+ case FILE_BEQDATE:
+ case FILE_BEQLDATE:
+ case FILE_BEQWDATE:
+ case FILE_LEQDATE:
+ case FILE_LEQLDATE:
+ case FILE_LEQWDATE:
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ case FILE_OFFSET:
+ case FILE_BEVARINT:
+ case FILE_LEVARINT:
+ v = CAST(int64_t, v);
+ break;
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ case FILE_REGEX:
+ case FILE_SEARCH:
+ case FILE_DEFAULT:
+ case FILE_INDIRECT:
+ case FILE_NAME:
+ case FILE_USE:
+ case FILE_CLEAR:
+ case FILE_DER:
+ case FILE_GUID:
+ case FILE_OCTAL:
+ break;
+ default:
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "cannot happen: m->type=%d\n",
+ m->type);
+ return FILE_BADSIZE;
+ }
+ }
+ return v;
+}
+
+file_private int
+string_modifier_check(struct magic_set *ms, struct magic *m)
+{
+ if ((ms->flags & MAGIC_CHECK) == 0)
+ return 0;
+
+ if ((m->type != FILE_REGEX || (m->str_flags & REGEX_LINE_COUNT) == 0) &&
+ (m->type != FILE_PSTRING && (m->str_flags & PSTRING_LEN) != 0)) {
+ file_magwarn(ms,
+ "'/BHhLl' modifiers are only allowed for pascal strings\n");
+ return -1;
+ }
+ switch (m->type) {
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ if (m->str_flags != 0) {
+ file_magwarn(ms,
+ "no modifiers allowed for 16-bit strings\n");
+ return -1;
+ }
+ break;
+ case FILE_STRING:
+ case FILE_PSTRING:
+ if ((m->str_flags & REGEX_OFFSET_START) != 0) {
+ file_magwarn(ms,
+ "'/%c' only allowed on regex and search\n",
+ CHAR_REGEX_OFFSET_START);
+ return -1;
+ }
+ break;
+ case FILE_SEARCH:
+ if (m->str_range == 0) {
+ file_magwarn(ms,
+ "missing range; defaulting to %d\n",
+ STRING_DEFAULT_RANGE);
+ m->str_range = STRING_DEFAULT_RANGE;
+ return -1;
+ }
+ break;
+ case FILE_REGEX:
+ if ((m->str_flags & STRING_COMPACT_WHITESPACE) != 0) {
+ file_magwarn(ms, "'/%c' not allowed on regex\n",
+ CHAR_COMPACT_WHITESPACE);
+ return -1;
+ }
+ if ((m->str_flags & STRING_COMPACT_OPTIONAL_WHITESPACE) != 0) {
+ file_magwarn(ms, "'/%c' not allowed on regex\n",
+ CHAR_COMPACT_OPTIONAL_WHITESPACE);
+ return -1;
+ }
+ break;
+ default:
+ file_magwarn(ms, "coding error: m->type=%d\n",
+ m->type);
+ return -1;
+ }
+ return 0;
+}
+
+file_private int
+get_op(char c)
+{
+ switch (c) {
+ case '&':
+ return FILE_OPAND;
+ case '|':
+ return FILE_OPOR;
+ case '^':
+ return FILE_OPXOR;
+ case '+':
+ return FILE_OPADD;
+ case '-':
+ return FILE_OPMINUS;
+ case '*':
+ return FILE_OPMULTIPLY;
+ case '/':
+ return FILE_OPDIVIDE;
+ case '%':
+ return FILE_OPMODULO;
+ default:
+ return -1;
+ }
+}
+
+#ifdef ENABLE_CONDITIONALS
+file_private int
+get_cond(const char *l, const char **t)
+{
+ static const struct cond_tbl_s {
+ char name[8];
+ size_t len;
+ int cond;
+ } cond_tbl[] = {
+ { "if", 2, COND_IF },
+ { "elif", 4, COND_ELIF },
+ { "else", 4, COND_ELSE },
+ { "", 0, COND_NONE },
+ };
+ const struct cond_tbl_s *p;
+
+ for (p = cond_tbl; p->len; p++) {
+ if (strncmp(l, p->name, p->len) == 0 &&
+ isspace(CAST(unsigned char, l[p->len]))) {
+ if (t)
+ *t = l + p->len;
+ break;
+ }
+ }
+ return p->cond;
+}
+
+file_private int
+check_cond(struct magic_set *ms, int cond, uint32_t cont_level)
+{
+ int last_cond;
+ last_cond = ms->c.li[cont_level].last_cond;
+
+ switch (cond) {
+ case COND_IF:
+ if (last_cond != COND_NONE && last_cond != COND_ELIF) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "syntax error: `if'");
+ return -1;
+ }
+ last_cond = COND_IF;
+ break;
+
+ case COND_ELIF:
+ if (last_cond != COND_IF && last_cond != COND_ELIF) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "syntax error: `elif'");
+ return -1;
+ }
+ last_cond = COND_ELIF;
+ break;
+
+ case COND_ELSE:
+ if (last_cond != COND_IF && last_cond != COND_ELIF) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "syntax error: `else'");
+ return -1;
+ }
+ last_cond = COND_NONE;
+ break;
+
+ case COND_NONE:
+ last_cond = COND_NONE;
+ break;
+ }
+
+ ms->c.li[cont_level].last_cond = last_cond;
+ return 0;
+}
+#endif /* ENABLE_CONDITIONALS */
+
+file_private int
+parse_indirect_modifier(struct magic_set *ms, struct magic *m, const char **lp)
+{
+ const char *l = *lp;
+
+ while (!isspace(CAST(unsigned char, *++l)))
+ switch (*l) {
+ case CHAR_INDIRECT_RELATIVE:
+ m->str_flags |= INDIRECT_RELATIVE;
+ break;
+ default:
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "indirect modifier `%c' "
+ "invalid", *l);
+ *lp = l;
+ return -1;
+ }
+ *lp = l;
+ return 0;
+}
+
+file_private void
+parse_op_modifier(struct magic_set *ms, struct magic *m, const char **lp,
+ int op)
+{
+ const char *l = *lp;
+ char *t;
+ uint64_t val;
+
+ ++l;
+ m->mask_op |= op;
+ val = CAST(uint64_t, strtoull(l, &t, 0));
+ l = t;
+ m->num_mask = file_signextend(ms, m, val);
+ eatsize(&l);
+ *lp = l;
+}
+
+file_private int
+parse_string_modifier(struct magic_set *ms, struct magic *m, const char **lp)
+{
+ const char *l = *lp;
+ char *t;
+ int have_range = 0;
+
+ while (!isspace(CAST(unsigned char, *++l))) {
+ switch (*l) {
+ case '0': case '1': case '2':
+ case '3': case '4': case '5':
+ case '6': case '7': case '8':
+ case '9':
+ if (have_range && (ms->flags & MAGIC_CHECK))
+ file_magwarn(ms, "multiple ranges");
+ have_range = 1;
+ m->str_range = CAST(uint32_t, strtoul(l, &t, 0));
+ if (m->str_range == 0)
+ file_magwarn(ms, "zero range");
+ l = t - 1;
+ break;
+ case CHAR_COMPACT_WHITESPACE:
+ m->str_flags |= STRING_COMPACT_WHITESPACE;
+ break;
+ case CHAR_COMPACT_OPTIONAL_WHITESPACE:
+ m->str_flags |= STRING_COMPACT_OPTIONAL_WHITESPACE;
+ break;
+ case CHAR_IGNORE_LOWERCASE:
+ m->str_flags |= STRING_IGNORE_LOWERCASE;
+ break;
+ case CHAR_IGNORE_UPPERCASE:
+ m->str_flags |= STRING_IGNORE_UPPERCASE;
+ break;
+ case CHAR_REGEX_OFFSET_START:
+ m->str_flags |= REGEX_OFFSET_START;
+ break;
+ case CHAR_BINTEST:
+ m->str_flags |= STRING_BINTEST;
+ break;
+ case CHAR_TEXTTEST:
+ m->str_flags |= STRING_TEXTTEST;
+ break;
+ case CHAR_TRIM:
+ m->str_flags |= STRING_TRIM;
+ break;
+ case CHAR_FULL_WORD:
+ m->str_flags |= STRING_FULL_WORD;
+ break;
+ case CHAR_PSTRING_1_LE:
+#define SET_LENGTH(a) m->str_flags = (m->str_flags & ~PSTRING_LEN) | (a)
+ if (m->type != FILE_PSTRING)
+ goto bad;
+ SET_LENGTH(PSTRING_1_LE);
+ break;
+ case CHAR_PSTRING_2_BE:
+ if (m->type != FILE_PSTRING)
+ goto bad;
+ SET_LENGTH(PSTRING_2_BE);
+ break;
+ case CHAR_PSTRING_2_LE:
+ if (m->type != FILE_PSTRING)
+ goto bad;
+ SET_LENGTH(PSTRING_2_LE);
+ break;
+ case CHAR_PSTRING_4_BE:
+ if (m->type != FILE_PSTRING)
+ goto bad;
+ SET_LENGTH(PSTRING_4_BE);
+ break;
+ case CHAR_PSTRING_4_LE:
+ switch (m->type) {
+ case FILE_PSTRING:
+ case FILE_REGEX:
+ break;
+ default:
+ goto bad;
+ }
+ SET_LENGTH(PSTRING_4_LE);
+ break;
+ case CHAR_PSTRING_LENGTH_INCLUDES_ITSELF:
+ if (m->type != FILE_PSTRING)
+ goto bad;
+ m->str_flags |= PSTRING_LENGTH_INCLUDES_ITSELF;
+ break;
+ default:
+ bad:
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "string modifier `%c' "
+ "invalid", *l);
+ goto out;
+ }
+ /* allow multiple '/' for readability */
+ if (l[1] == '/' && !isspace(CAST(unsigned char, l[2])))
+ l++;
+ }
+ if (string_modifier_check(ms, m) == -1)
+ goto out;
+ *lp = l;
+ return 0;
+out:
+ *lp = l;
+ return -1;
+}
+
+/*
+ * parse one line from magic file, put into magic[index++] if valid
+ */
+file_private int
+parse(struct magic_set *ms, struct magic_entry *me, const char *line,
+ size_t lineno, int action)
+{
+#ifdef ENABLE_CONDITIONALS
+ static uint32_t last_cont_level = 0;
+#endif
+ size_t i;
+ struct magic *m;
+ const char *l = line;
+ char *t;
+ int op;
+ uint32_t cont_level;
+ int32_t diff;
+
+ cont_level = 0;
+
+ /*
+ * Parse the offset.
+ */
+ while (*l == '>') {
+ ++l; /* step over */
+ cont_level++;
+ }
+#ifdef ENABLE_CONDITIONALS
+ if (cont_level == 0 || cont_level > last_cont_level)
+ if (file_check_mem(ms, cont_level) == -1)
+ return -1;
+ last_cont_level = cont_level;
+#endif
+ if (cont_level != 0) {
+ if (me->mp == NULL) {
+ file_magerror(ms, "No current entry for continuation");
+ return -1;
+ }
+ if (me->cont_count == 0) {
+ file_magerror(ms, "Continuations present with 0 count");
+ return -1;
+ }
+ m = &me->mp[me->cont_count - 1];
+ diff = CAST(int32_t, cont_level) - CAST(int32_t, m->cont_level);
+ if (diff > 1)
+ file_magwarn(ms, "New continuation level %u is more "
+ "than one larger than current level %u", cont_level,
+ m->cont_level);
+ if (me->cont_count == me->max_count) {
+ struct magic *nm;
+ size_t cnt = me->max_count + ALLOC_CHUNK;
+ if ((nm = CAST(struct magic *, realloc(me->mp,
+ sizeof(*nm) * cnt))) == NULL) {
+ file_oomem(ms, sizeof(*nm) * cnt);
+ return -1;
+ }
+ me->mp = nm;
+ me->max_count = CAST(uint32_t, cnt);
+ }
+ m = &me->mp[me->cont_count++];
+ (void)memset(m, 0, sizeof(*m));
+ m->cont_level = cont_level;
+ } else {
+ static const size_t len = sizeof(*m) * ALLOC_CHUNK;
+ if (me->mp != NULL)
+ return 1;
+ if ((m = CAST(struct magic *, malloc(len))) == NULL) {
+ file_oomem(ms, len);
+ return -1;
+ }
+ me->mp = m;
+ me->max_count = ALLOC_CHUNK;
+ (void)memset(m, 0, sizeof(*m));
+ m->factor_op = FILE_FACTOR_OP_NONE;
+ m->cont_level = 0;
+ me->cont_count = 1;
+ }
+ m->lineno = CAST(uint32_t, lineno);
+
+ if (*l == '&') { /* m->cont_level == 0 checked below. */
+ ++l; /* step over */
+ m->flag |= OFFADD;
+ }
+ if (*l == '(') {
+ ++l; /* step over */
+ m->flag |= INDIR;
+ if (m->flag & OFFADD)
+ m->flag = (m->flag & ~OFFADD) | INDIROFFADD;
+
+ if (*l == '&') { /* m->cont_level == 0 checked below */
+ ++l; /* step over */
+ m->flag |= OFFADD;
+ }
+ }
+ /* Indirect offsets are not valid at level 0. */
+ if (m->cont_level == 0 && (m->flag & (OFFADD | INDIROFFADD))) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "relative offset at level 0");
+ return -1;
+ }
+
+ /* get offset, then skip over it */
+ if (*l == '-') {
+ ++l; /* step over */
+ m->flag |= OFFNEGATIVE;
+ }
+ m->offset = CAST(int32_t, strtol(l, &t, 0));
+ if (l == t) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "offset `%s' invalid", l);
+ return -1;
+ }
+
+ l = t;
+
+ if (m->flag & INDIR) {
+ m->in_type = FILE_LONG;
+ m->in_offset = 0;
+ m->in_op = 0;
+ /*
+ * read [.,lbs][+-]nnnnn)
+ */
+ if (*l == '.' || *l == ',') {
+ if (*l == ',')
+ m->in_op |= FILE_OPSIGNED;
+ l++;
+ switch (*l) {
+ case 'l':
+ m->in_type = FILE_LELONG;
+ break;
+ case 'L':
+ m->in_type = FILE_BELONG;
+ break;
+ case 'm':
+ m->in_type = FILE_MELONG;
+ break;
+ case 'h':
+ case 's':
+ m->in_type = FILE_LESHORT;
+ break;
+ case 'H':
+ case 'S':
+ m->in_type = FILE_BESHORT;
+ break;
+ case 'c':
+ case 'b':
+ case 'C':
+ case 'B':
+ m->in_type = FILE_BYTE;
+ break;
+ case 'e':
+ case 'f':
+ case 'g':
+ m->in_type = FILE_LEDOUBLE;
+ break;
+ case 'E':
+ case 'F':
+ case 'G':
+ m->in_type = FILE_BEDOUBLE;
+ break;
+ case 'i':
+ m->in_type = FILE_LEID3;
+ break;
+ case 'I':
+ m->in_type = FILE_BEID3;
+ break;
+ case 'o':
+ m->in_type = FILE_OCTAL;
+ break;
+ case 'q':
+ m->in_type = FILE_LEQUAD;
+ break;
+ case 'Q':
+ m->in_type = FILE_BEQUAD;
+ break;
+ default:
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms,
+ "indirect offset type `%c' invalid",
+ *l);
+ return -1;
+ }
+ l++;
+ }
+
+ if (*l == '~') {
+ m->in_op |= FILE_OPINVERSE;
+ l++;
+ }
+ if ((op = get_op(*l)) != -1) {
+ m->in_op |= op;
+ l++;
+ }
+ if (*l == '(') {
+ m->in_op |= FILE_OPINDIRECT;
+ l++;
+ }
+ if (isdigit(CAST(unsigned char, *l)) || *l == '-') {
+ m->in_offset = CAST(int32_t, strtol(l, &t, 0));
+ if (l == t) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms,
+ "in_offset `%s' invalid", l);
+ return -1;
+ }
+ l = t;
+ }
+ if (*l++ != ')' ||
+ ((m->in_op & FILE_OPINDIRECT) && *l++ != ')')) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms,
+ "missing ')' in indirect offset");
+ return -1;
+ }
+ }
+ EATAB;
+
+#ifdef ENABLE_CONDITIONALS
+ m->cond = get_cond(l, &l);
+ if (check_cond(ms, m->cond, cont_level) == -1)
+ return -1;
+
+ EATAB;
+#endif
+
+ /*
+ * Parse the type.
+ */
+ if (*l == 'u') {
+ /*
+ * Try it as a keyword type prefixed by "u"; match what
+ * follows the "u". If that fails, try it as an SUS
+ * integer type.
+ */
+ m->type = get_type(type_tbl, l + 1, &l);
+ if (m->type == FILE_INVALID) {
+ /*
+ * Not a keyword type; parse it as an SUS type,
+ * 'u' possibly followed by a number or C/S/L.
+ */
+ m->type = get_standard_integer_type(l, &l);
+ }
+ /* It's unsigned. */
+ if (m->type != FILE_INVALID)
+ m->flag |= UNSIGNED;
+ } else {
+ /*
+ * Try it as a keyword type. If that fails, try it as
+ * an SUS integer type if it begins with "d" or as an
+ * SUS string type if it begins with "s". In any case,
+ * it's not unsigned.
+ */
+ m->type = get_type(type_tbl, l, &l);
+ if (m->type == FILE_INVALID) {
+ /*
+ * Not a keyword type; parse it as an SUS type,
+ * either 'd' possibly followed by a number or
+ * C/S/L, or just 's'.
+ */
+ if (*l == 'd')
+ m->type = get_standard_integer_type(l, &l);
+ else if (*l == 's'
+ && !isalpha(CAST(unsigned char, l[1]))) {
+ m->type = FILE_STRING;
+ ++l;
+ }
+ }
+ }
+
+ if (m->type == FILE_INVALID) {
+ /* Not found - try it as a special keyword. */
+ m->type = get_type(special_tbl, l, &l);
+ }
+
+ if (m->type == FILE_INVALID) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "type `%s' invalid", l);
+ return -1;
+ }
+
+ if (m->type == FILE_NAME && cont_level != 0) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "`name%s' entries can only be "
+ "declared at top level", l);
+ return -1;
+ }
+
+ /* New-style anding: "0 byte&0x80 =0x80 dynamically linked" */
+ /* New and improved: ~ & | ^ + - * / % -- exciting, isn't it? */
+
+ m->mask_op = 0;
+ if (*l == '~') {
+ if (!IS_STRING(m->type))
+ m->mask_op |= FILE_OPINVERSE;
+ else if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "'~' invalid for string types");
+ ++l;
+ }
+ m->str_range = 0;
+ m->str_flags = m->type == FILE_PSTRING ? PSTRING_1_LE : 0;
+ if ((op = get_op(*l)) != -1) {
+ if (IS_STRING(m->type)) {
+ int r;
+
+ if (op != FILE_OPDIVIDE) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms,
+ "invalid string/indirect op: "
+ "`%c'", *t);
+ return -1;
+ }
+
+ if (m->type == FILE_INDIRECT)
+ r = parse_indirect_modifier(ms, m, &l);
+ else
+ r = parse_string_modifier(ms, m, &l);
+ if (r == -1)
+ return -1;
+ } else
+ parse_op_modifier(ms, m, &l, op);
+ }
+
+ /*
+ * We used to set mask to all 1's here, instead let's just not do
+ * anything if mask = 0 (unless you have a better idea)
+ */
+ EATAB;
+
+ switch (*l) {
+ case '>':
+ case '<':
+ m->reln = *l;
+ ++l;
+ if (*l == '=') {
+ if (ms->flags & MAGIC_CHECK) {
+ file_magwarn(ms, "%c= not supported",
+ m->reln);
+ return -1;
+ }
+ ++l;
+ }
+ break;
+ /* Old-style anding: "0 byte &0x80 dynamically linked" */
+ case '&':
+ case '^':
+ case '=':
+ m->reln = *l;
+ ++l;
+ if (*l == '=') {
+ /* HP compat: ignore &= etc. */
+ ++l;
+ }
+ break;
+ case '!':
+ m->reln = *l;
+ ++l;
+ break;
+ default:
+ m->reln = '='; /* the default relation */
+ if (*l == 'x' && ((isascii(CAST(unsigned char, l[1])) &&
+ isspace(CAST(unsigned char, l[1]))) || !l[1])) {
+ m->reln = *l;
+ ++l;
+ }
+ break;
+ }
+ /*
+ * Grab the value part, except for an 'x' reln.
+ */
+ if (m->reln != 'x' && getvalue(ms, m, &l, action))
+ return -1;
+
+ /*
+ * TODO finish this macro and start using it!
+ * #define offsetcheck {if (offset > ms->bytes_max -1)
+ * magwarn("offset too big"); }
+ */
+
+ /*
+ * Now get last part - the description
+ */
+ EATAB;
+ if (l[0] == '\b') {
+ ++l;
+ m->flag |= NOSPACE;
+ } else if ((l[0] == '\\') && (l[1] == 'b')) {
+ ++l;
+ ++l;
+ m->flag |= NOSPACE;
+ }
+ for (i = 0; (m->desc[i++] = *l++) != '\0' && i < sizeof(m->desc); )
+ continue;
+ if (i == sizeof(m->desc)) {
+ m->desc[sizeof(m->desc) - 1] = '\0';
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "description `%s' truncated", m->desc);
+ }
+
+ /*
+ * We only do this check while compiling, or if any of the magic
+ * files were not compiled.
+ */
+ if (ms->flags & MAGIC_CHECK) {
+ if (check_format(ms, m) == -1)
+ return -1;
+ }
+#ifndef COMPILE_ONLY
+ if (action == FILE_CHECK) {
+ file_mdump(m);
+ }
+#endif
+ m->mimetype[0] = '\0'; /* initialise MIME type to none */
+ return 0;
+}
+
+/*
+ * parse a STRENGTH annotation line from magic file, put into magic[index - 1]
+ * if valid
+ */
+/*ARGSUSED*/
+file_private int
+parse_strength(struct magic_set *ms, struct magic_entry *me, const char *line,
+ size_t len __attribute__((__unused__)))
+{
+ const char *l = line;
+ char *el;
+ unsigned long factor;
+ char sbuf[512];
+ struct magic *m = &me->mp[0];
+
+ if (m->factor_op != FILE_FACTOR_OP_NONE) {
+ file_magwarn(ms,
+ "Current entry already has a strength type: %c %d",
+ m->factor_op, m->factor);
+ return -1;
+ }
+ if (m->type == FILE_NAME) {
+ file_magwarn(ms, "%s: Strength setting is not supported in "
+ "\"name\" magic entries",
+ file_printable(ms, sbuf, sizeof(sbuf), m->value.s,
+ sizeof(m->value.s)));
+ return -1;
+ }
+ EATAB;
+ switch (*l) {
+ case FILE_FACTOR_OP_NONE:
+ break;
+ case FILE_FACTOR_OP_PLUS:
+ case FILE_FACTOR_OP_MINUS:
+ case FILE_FACTOR_OP_TIMES:
+ case FILE_FACTOR_OP_DIV:
+ m->factor_op = *l++;
+ break;
+ default:
+ file_magwarn(ms, "Unknown factor op `%c'", *l);
+ return -1;
+ }
+ EATAB;
+ factor = strtoul(l, &el, 0);
+ if (factor > 255) {
+ file_magwarn(ms, "Too large factor `%lu'", factor);
+ goto out;
+ }
+ if (*el && !isspace(CAST(unsigned char, *el))) {
+ file_magwarn(ms, "Bad factor `%s'", l);
+ goto out;
+ }
+ m->factor = CAST(uint8_t, factor);
+ if (m->factor == 0 && m->factor_op == FILE_FACTOR_OP_DIV) {
+ file_magwarn(ms, "Cannot have factor op `%c' and factor %u",
+ m->factor_op, m->factor);
+ goto out;
+ }
+ return 0;
+out:
+ m->factor_op = FILE_FACTOR_OP_NONE;
+ m->factor = 0;
+ return -1;
+}
+
+file_private int
+goodchar(unsigned char x, const char *extra)
+{
+ return (isascii(x) && isalnum(x)) || strchr(extra, x);
+}
+
+file_private int
+parse_extra(struct magic_set *ms, struct magic_entry *me, const char *line,
+ size_t llen, off_t off, size_t len, const char *name, const char *extra,
+ int nt)
+{
+ size_t i;
+ const char *l = line;
+ struct magic *m = &me->mp[me->cont_count == 0 ? 0 : me->cont_count - 1];
+ char *buf = CAST(char *, CAST(void *, m)) + off;
+
+ if (buf[0] != '\0') {
+ len = nt ? strlen(buf) : len;
+ file_magwarn(ms, "Current entry already has a %s type "
+ "`%.*s', new type `%s'", name, CAST(int, len), buf, l);
+ return -1;
+ }
+
+ if (*m->desc == '\0') {
+ file_magwarn(ms, "Current entry does not yet have a "
+ "description for adding a %s type", name);
+ return -1;
+ }
+
+ EATAB;
+ for (i = 0; *l && i < llen && i < len && goodchar(*l, extra);
+ buf[i++] = *l++)
+ continue;
+
+ if (i == len && *l) {
+ if (nt)
+ buf[len - 1] = '\0';
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "%s type `%s' truncated %"
+ SIZE_T_FORMAT "u", name, line, i);
+ } else {
+ if (!isspace(CAST(unsigned char, *l)) && !goodchar(*l, extra))
+ file_magwarn(ms, "%s type `%s' has bad char '%c'",
+ name, line, *l);
+ if (nt)
+ buf[i] = '\0';
+ }
+
+ if (i > 0)
+ return 0;
+
+ file_magerror(ms, "Bad magic entry '%s'", line);
+ return -1;
+}
+
+/*
+ * Parse an Apple CREATOR/TYPE annotation from magic file and put it into
+ * magic[index - 1]
+ */
+file_private int
+parse_apple(struct magic_set *ms, struct magic_entry *me, const char *line,
+ size_t len)
+{
+ return parse_extra(ms, me, line, len,
+ CAST(off_t, offsetof(struct magic, apple)),
+ sizeof(me->mp[0].apple), "APPLE", "!+-./?", 0);
+}
+
+/*
+ * Parse a comma-separated list of extensions
+ */
+file_private int
+parse_ext(struct magic_set *ms, struct magic_entry *me, const char *line,
+ size_t len)
+{
+ return parse_extra(ms, me, line, len,
+ CAST(off_t, offsetof(struct magic, ext)),
+ sizeof(me->mp[0].ext), "EXTENSION", ",!+-/@?_$&", 0); /* & for b&w */
+}
+
+/*
+ * parse a MIME annotation line from magic file, put into magic[index - 1]
+ * if valid
+ */
+file_private int
+parse_mime(struct magic_set *ms, struct magic_entry *me, const char *line,
+ size_t len)
+{
+ return parse_extra(ms, me, line, len,
+ CAST(off_t, offsetof(struct magic, mimetype)),
+ sizeof(me->mp[0].mimetype), "MIME", "+-/.$?:{}", 1);
+}
+
+file_private int
+check_format_type(const char *ptr, int type, const char **estr)
+{
+ int quad = 0, h;
+ size_t len, cnt;
+ if (*ptr == '\0') {
+ /* Missing format string; bad */
+ *estr = "missing format spec";
+ return -1;
+ }
+
+ switch (file_formats[type]) {
+ case FILE_FMT_QUAD:
+ quad = 1;
+ /*FALLTHROUGH*/
+ case FILE_FMT_NUM:
+ if (quad == 0) {
+ switch (type) {
+ case FILE_BYTE:
+ h = 2;
+ break;
+ case FILE_SHORT:
+ case FILE_BESHORT:
+ case FILE_LESHORT:
+ h = 1;
+ break;
+ case FILE_LONG:
+ case FILE_BELONG:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ case FILE_LEID3:
+ case FILE_BEID3:
+ case FILE_INDIRECT:
+ h = 0;
+ break;
+ default:
+ fprintf(stderr, "Bad number format %d", type);
+ abort();
+ }
+ } else
+ h = 0;
+ while (*ptr && strchr("-.#", *ptr) != NULL)
+ ptr++;
+#define CHECKLEN() do { \
+ for (len = cnt = 0; isdigit(CAST(unsigned char, *ptr)); ptr++, cnt++) \
+ len = len * 10 + (*ptr - '0'); \
+ if (cnt > 5 || len > 1024) \
+ goto toolong; \
+} while (/*CONSTCOND*/0)
+
+ CHECKLEN();
+ if (*ptr == '.')
+ ptr++;
+ CHECKLEN();
+ if (quad) {
+ if (*ptr++ != 'l')
+ goto invalid;
+ if (*ptr++ != 'l')
+ goto invalid;
+ }
+
+ switch (*ptr++) {
+#ifdef STRICT_FORMAT /* "long" formats are int formats for us */
+ /* so don't accept the 'l' modifier */
+ case 'l':
+ switch (*ptr++) {
+ case 'i':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ if (h == 0)
+ return 0;
+ /*FALLTHROUGH*/
+ default:
+ goto invalid;
+ }
+
+ /*
+ * Don't accept h and hh modifiers. They make writing
+ * magic entries more complicated, for very little benefit
+ */
+ case 'h':
+ if (h-- <= 0)
+ goto invalid;
+ switch (*ptr++) {
+ case 'h':
+ if (h-- <= 0)
+ goto invalid;
+ switch (*ptr++) {
+ case 'i':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ return 0;
+ default:
+ goto invalid;
+ }
+ case 'i':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ if (h == 0)
+ return 0;
+ /*FALLTHROUGH*/
+ default:
+ goto invalid;
+ }
+#endif
+ case 'c':
+ if (h == 2)
+ return 0;
+ goto invalid;
+ case 'i':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+#ifdef STRICT_FORMAT
+ if (h == 0)
+ return 0;
+ /*FALLTHROUGH*/
+#else
+ return 0;
+#endif
+ default:
+ goto invalid;
+ }
+
+ case FILE_FMT_FLOAT:
+ case FILE_FMT_DOUBLE:
+ if (*ptr == '-')
+ ptr++;
+ if (*ptr == '.')
+ ptr++;
+ CHECKLEN();
+ if (*ptr == '.')
+ ptr++;
+ CHECKLEN();
+ switch (*ptr++) {
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ return 0;
+
+ default:
+ goto invalid;
+ }
+
+
+ case FILE_FMT_STR:
+ if (*ptr == '-')
+ ptr++;
+ while (isdigit(CAST(unsigned char, *ptr)))
+ ptr++;
+ if (*ptr == '.') {
+ ptr++;
+ while (isdigit(CAST(unsigned char , *ptr)))
+ ptr++;
+ }
+
+ switch (*ptr++) {
+ case 's':
+ return 0;
+ default:
+ goto invalid;
+ }
+
+ default:
+ /* internal error */
+ fprintf(stderr, "Bad file format %d", type);
+ abort();
+ }
+invalid:
+ *estr = "not valid";
+ return -1;
+toolong:
+ *estr = "too long";
+ return -1;
+}
+
+/*
+ * Check that the optional printf format in description matches
+ * the type of the magic.
+ */
+file_private int
+check_format(struct magic_set *ms, struct magic *m)
+{
+ char *ptr;
+ const char *estr;
+
+ for (ptr = m->desc; *ptr; ptr++)
+ if (*ptr == '%')
+ break;
+ if (*ptr == '\0') {
+ /* No format string; ok */
+ return 1;
+ }
+
+ assert(file_nformats == file_nnames);
+
+ if (m->type >= file_nformats) {
+ file_magwarn(ms, "Internal error inconsistency between "
+ "m->type and format strings");
+ return -1;
+ }
+ if (file_formats[m->type] == FILE_FMT_NONE) {
+ file_magwarn(ms, "No format string for `%s' with description "
+ "`%s'", m->desc, file_names[m->type]);
+ return -1;
+ }
+
+ ptr++;
+ if (check_format_type(ptr, m->type, &estr) == -1) {
+ /*
+ * TODO: this error message is unhelpful if the format
+ * string is not one character long
+ */
+ file_magwarn(ms, "Printf format is %s for type "
+ "`%s' in description `%s'", estr,
+ file_names[m->type], m->desc);
+ return -1;
+ }
+
+ for (; *ptr; ptr++) {
+ if (*ptr == '%') {
+ file_magwarn(ms,
+ "Too many format strings (should have at most one) "
+ "for `%s' with description `%s'",
+ file_names[m->type], m->desc);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Read a numeric value from a pointer, into the value union of a magic
+ * pointer, according to the magic type. Update the string pointer to point
+ * just after the number read. Return 0 for success, non-zero for failure.
+ */
+file_private int
+getvalue(struct magic_set *ms, struct magic *m, const char **p, int action)
+{
+ char *ep;
+ uint64_t ull;
+ int y;
+
+ switch (m->type) {
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_REGEX:
+ case FILE_SEARCH:
+ case FILE_NAME:
+ case FILE_USE:
+ case FILE_DER:
+ case FILE_OCTAL:
+ *p = getstr(ms, m, *p, action == FILE_COMPILE);
+ if (*p == NULL) {
+ if (ms->flags & MAGIC_CHECK)
+ file_magwarn(ms, "cannot get string from `%s'",
+ m->value.s);
+ return -1;
+ }
+ if (m->type == FILE_REGEX) {
+ file_regex_t rx;
+ int rc =
+ file_regcomp(ms, &rx, m->value.s, REG_EXTENDED);
+ if (rc == 0) {
+ file_regfree(&rx);
+ }
+ return rc ? -1 : 0;
+ }
+ return 0;
+ default:
+ if (m->reln == 'x')
+ return 0;
+ break;
+ }
+
+ switch (m->type) {
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ errno = 0;
+#ifdef HAVE_STRTOF
+ m->value.f = strtof(*p, &ep);
+#else
+ m->value.f = (float)strtod(*p, &ep);
+#endif
+ if (errno == 0)
+ *p = ep;
+ return 0;
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ errno = 0;
+ m->value.d = strtod(*p, &ep);
+ if (errno == 0)
+ *p = ep;
+ return 0;
+ case FILE_GUID:
+ if (file_parse_guid(*p, m->value.guid) == -1)
+ return -1;
+ *p += FILE_GUID_SIZE - 1;
+ return 0;
+ default:
+ errno = 0;
+ ull = CAST(uint64_t, strtoull(*p, &ep, 0));
+ m->value.q = file_signextend(ms, m, ull);
+ if (*p == ep) {
+ file_magwarn(ms, "Unparsable number `%s'", *p);
+ return -1;
+ } else {
+ size_t ts = typesize(m->type);
+ uint64_t x;
+ const char *q;
+
+ if (ts == FILE_BADSIZE) {
+ file_magwarn(ms,
+ "Expected numeric type got `%s'",
+ type_tbl[m->type].name);
+ return -1;
+ }
+ for (q = *p; isspace(CAST(unsigned char, *q)); q++)
+ continue;
+ if (*q == '-' && ull != UINT64_MAX)
+ ull = -CAST(int64_t, ull);
+ switch (ts) {
+ case 1:
+ x = CAST(uint64_t, ull & ~0xffULL);
+ y = (x & ~0xffULL) != ~0xffULL;
+ break;
+ case 2:
+ x = CAST(uint64_t, ull & ~0xffffULL);
+ y = (x & ~0xffffULL) != ~0xffffULL;
+ break;
+ case 4:
+ x = CAST(uint64_t, ull & ~0xffffffffULL);
+ y = (x & ~0xffffffffULL) != ~0xffffffffULL;
+ break;
+ case 8:
+ x = 0;
+ y = 0;
+ break;
+ default:
+ fprintf(stderr, "Bad width %zu", ts);
+ abort();
+ }
+ if (x && y) {
+ file_magwarn(ms, "Overflow for numeric"
+ " type `%s' value %#" PRIx64,
+ type_tbl[m->type].name, ull);
+ return -1;
+ }
+ }
+ if (errno == 0) {
+ *p = ep;
+ eatsize(p);
+ }
+ return 0;
+ }
+}
+
+/*
+ * Convert a string containing C character escapes. Stop at an unescaped
+ * space or tab.
+ * Copy the converted version to "m->value.s", and the length in m->vallen.
+ * Return updated scan pointer as function result. Warn if set.
+ */
+file_private const char *
+getstr(struct magic_set *ms, struct magic *m, const char *s, int warn)
+{
+ const char *origs = s;
+ char *p = m->value.s;
+ size_t plen = sizeof(m->value.s);
+ char *origp = p;
+ char *pmax = p + plen - 1;
+ int c;
+ int val;
+ size_t bracket_nesting = 0;
+
+ while ((c = *s++) != '\0') {
+ if (isspace(CAST(unsigned char, c)))
+ break;
+ if (p >= pmax) {
+ file_error(ms, 0, "string too long: `%s'", origs);
+ return NULL;
+ }
+ if (c != '\\') {
+ if (c == '[') {
+ bracket_nesting++;
+ }
+ if (c == ']' && bracket_nesting > 0) {
+ bracket_nesting--;
+ }
+ *p++ = CAST(char, c);
+ continue;
+ }
+ switch(c = *s++) {
+
+ case '\0':
+ if (warn)
+ file_magwarn(ms, "incomplete escape");
+ s--;
+ goto out;
+ case '.':
+ if (m->type == FILE_REGEX &&
+ bracket_nesting == 0 && warn) {
+ file_magwarn(ms, "escaped dot ('.') found, "
+ "use \\\\. instead");
+ }
+ warn = 0; /* already did */
+ /*FALLTHROUGH*/
+ case '\t':
+ if (warn) {
+ file_magwarn(ms,
+ "escaped tab found, use \\\\t instead");
+ warn = 0; /* already did */
+ }
+ /*FALLTHROUGH*/
+ default:
+ if (warn) {
+ if (isprint(CAST(unsigned char, c))) {
+ /* Allow escaping of
+ * ``relations'' */
+ if (strchr("<>&^=!", c) == NULL
+ && (m->type != FILE_REGEX ||
+ strchr("[]().*?^$|{}", c)
+ == NULL)) {
+ file_magwarn(ms, "no "
+ "need to escape "
+ "`%c'", c);
+ }
+ } else {
+ file_magwarn(ms,
+ "unknown escape sequence: "
+ "\\%03o", c);
+ }
+ }
+ /*FALLTHROUGH*/
+ /* space, perhaps force people to use \040? */
+ case ' ':
+#if 0
+ /*
+ * Other things people escape, but shouldn't need to,
+ * so we disallow them
+ */
+ case '\'':
+ case '"':
+ case '?':
+#endif
+ /* Relations */
+ case '>':
+ case '<':
+ case '&':
+ case '^':
+ case '=':
+ case '!':
+ /* and backslash itself */
+ case '\\':
+ *p++ = CAST(char, c);
+ break;
+
+ case 'a':
+ *p++ = '\a';
+ break;
+
+ case 'b':
+ *p++ = '\b';
+ break;
+
+ case 'f':
+ *p++ = '\f';
+ break;
+
+ case 'n':
+ *p++ = '\n';
+ break;
+
+ case 'r':
+ *p++ = '\r';
+ break;
+
+ case 't':
+ *p++ = '\t';
+ break;
+
+ case 'v':
+ *p++ = '\v';
+ break;
+
+ /* \ and up to 3 octal digits */
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = c - '0';
+ c = *s++; /* try for 2 */
+ if (c >= '0' && c <= '7') {
+ val = (val << 3) | (c - '0');
+ c = *s++; /* try for 3 */
+ if (c >= '0' && c <= '7')
+ val = (val << 3) | (c-'0');
+ else
+ --s;
+ }
+ else
+ --s;
+ *p++ = CAST(char, val);
+ break;
+
+ /* \x and up to 2 hex digits */
+ case 'x':
+ val = 'x'; /* Default if no digits */
+ c = hextoint(*s++); /* Get next char */
+ if (c >= 0) {
+ val = c;
+ c = hextoint(*s++);
+ if (c >= 0)
+ val = (val << 4) + c;
+ else
+ --s;
+ } else
+ --s;
+ *p++ = CAST(char, val);
+ break;
+ }
+ }
+ --s;
+out:
+ *p = '\0';
+ m->vallen = CAST(unsigned char, (p - origp));
+ if (m->type == FILE_PSTRING) {
+ size_t l = file_pstring_length_size(ms, m);
+ if (l == FILE_BADSIZE)
+ return NULL;
+ m->vallen += CAST(unsigned char, l);
+ }
+ return s;
+}
+
+
+/* Single hex char to int; -1 if not a hex char. */
+file_private int
+hextoint(int c)
+{
+ if (!isascii(CAST(unsigned char, c)))
+ return -1;
+ if (isdigit(CAST(unsigned char, c)))
+ return c - '0';
+ if ((c >= 'a') && (c <= 'f'))
+ return c + 10 - 'a';
+ if (( c>= 'A') && (c <= 'F'))
+ return c + 10 - 'A';
+ return -1;
+}
+
+
+/*
+ * Print a string containing C character escapes.
+ */
+file_protected void
+file_showstr(FILE *fp, const char *s, size_t len)
+{
+ char c;
+
+ for (;;) {
+ if (len == FILE_BADSIZE) {
+ c = *s++;
+ if (c == '\0')
+ break;
+ }
+ else {
+ if (len-- == 0)
+ break;
+ c = *s++;
+ }
+ if (c >= 040 && c <= 0176) /* TODO isprint && !iscntrl */
+ (void) fputc(c, fp);
+ else {
+ (void) fputc('\\', fp);
+ switch (c) {
+ case '\a':
+ (void) fputc('a', fp);
+ break;
+
+ case '\b':
+ (void) fputc('b', fp);
+ break;
+
+ case '\f':
+ (void) fputc('f', fp);
+ break;
+
+ case '\n':
+ (void) fputc('n', fp);
+ break;
+
+ case '\r':
+ (void) fputc('r', fp);
+ break;
+
+ case '\t':
+ (void) fputc('t', fp);
+ break;
+
+ case '\v':
+ (void) fputc('v', fp);
+ break;
+
+ default:
+ (void) fprintf(fp, "%.3o", c & 0377);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * eatsize(): Eat the size spec from a number [eg. 10UL]
+ */
+file_private void
+eatsize(const char **p)
+{
+ const char *l = *p;
+
+ if (LOWCASE(*l) == 'u')
+ l++;
+
+ switch (LOWCASE(*l)) {
+ case 'l': /* long */
+ case 's': /* short */
+ case 'h': /* short */
+ case 'b': /* char/byte */
+ case 'c': /* char/byte */
+ l++;
+ /*FALLTHROUGH*/
+ default:
+ break;
+ }
+
+ *p = l;
+}
+
+/*
+ * handle a buffer containing a compiled file.
+ */
+file_private struct magic_map *
+apprentice_buf(struct magic_set *ms, struct magic *buf, size_t len)
+{
+ struct magic_map *map;
+
+ if ((map = CAST(struct magic_map *, calloc(1, sizeof(*map)))) == NULL) {
+ file_oomem(ms, sizeof(*map));
+ return NULL;
+ }
+ map->len = len;
+ map->p = buf;
+ map->type = MAP_TYPE_USER;
+ if (check_buffer(ms, map, "buffer") != 0) {
+ apprentice_unmap(map);
+ return NULL;
+ }
+ return map;
+}
+
+/*
+ * handle a compiled file.
+ */
+
+file_private struct magic_map *
+apprentice_map(struct magic_set *ms, const char *fn)
+{
+ int fd;
+ struct stat st;
+ char *dbname = NULL;
+ struct magic_map *map;
+ struct magic_map *rv = NULL;
+
+ fd = -1;
+ if ((map = CAST(struct magic_map *, calloc(1, sizeof(*map)))) == NULL) {
+ file_oomem(ms, sizeof(*map));
+ goto error;
+ }
+ map->type = MAP_TYPE_USER; /* unspecified */
+
+ dbname = mkdbname(ms, fn, 0);
+ if (dbname == NULL)
+ goto error;
+
+ if ((fd = open(dbname, O_RDONLY|O_BINARY)) == -1)
+ goto error;
+
+ if (fstat(fd, &st) == -1) {
+ file_error(ms, errno, "cannot stat `%s'", dbname);
+ goto error;
+ }
+ if (st.st_size < 8 || st.st_size > maxoff_t()) {
+ file_error(ms, 0, "file `%s' is too %s", dbname,
+ st.st_size < 8 ? "small" : "large");
+ goto error;
+ }
+
+ map->len = CAST(size_t, st.st_size);
+#ifdef QUICK
+ map->type = MAP_TYPE_MMAP;
+ if ((map->p = mmap(0, CAST(size_t, st.st_size), PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_FILE, fd, CAST(off_t, 0))) == MAP_FAILED) {
+ file_error(ms, errno, "cannot map `%s'", dbname);
+ goto error;
+ }
+#else
+ map->type = MAP_TYPE_MALLOC;
+ if ((map->p = CAST(void *, malloc(map->len))) == NULL) {
+ file_oomem(ms, map->len);
+ goto error;
+ }
+ if (read(fd, map->p, map->len) != (ssize_t)map->len) {
+ file_badread(ms);
+ goto error;
+ }
+#endif
+ (void)close(fd);
+ fd = -1;
+
+ if (check_buffer(ms, map, dbname) != 0) {
+ goto error;
+ }
+#ifdef QUICK
+ if (mprotect(map->p, CAST(size_t, st.st_size), PROT_READ) == -1) {
+ file_error(ms, errno, "cannot mprotect `%s'", dbname);
+ goto error;
+ }
+#endif
+
+ free(dbname);
+ return map;
+
+error:
+ if (fd != -1)
+ (void)close(fd);
+ apprentice_unmap(map);
+ free(dbname);
+ return rv;
+}
+
+file_private int
+check_buffer(struct magic_set *ms, struct magic_map *map, const char *dbname)
+{
+ uint32_t *ptr;
+ uint32_t entries, nentries;
+ uint32_t version;
+ int i, needsbyteswap;
+
+ ptr = CAST(uint32_t *, map->p);
+ if (*ptr != MAGICNO) {
+ if (swap4(*ptr) != MAGICNO) {
+ file_error(ms, 0, "bad magic in `%s'", dbname);
+ return -1;
+ }
+ needsbyteswap = 1;
+ } else
+ needsbyteswap = 0;
+ if (needsbyteswap)
+ version = swap4(ptr[1]);
+ else
+ version = ptr[1];
+ if (version != VERSIONNO) {
+ file_error(ms, 0, "File %s supports only version %d magic "
+ "files. `%s' is version %d", VERSION,
+ VERSIONNO, dbname, version);
+ return -1;
+ }
+ entries = CAST(uint32_t, map->len / sizeof(struct magic));
+ if ((entries * sizeof(struct magic)) != map->len) {
+ file_error(ms, 0, "Size of `%s' %" SIZE_T_FORMAT "u is not "
+ "a multiple of %" SIZE_T_FORMAT "u",
+ dbname, map->len, sizeof(struct magic));
+ return -1;
+ }
+ map->magic[0] = CAST(struct magic *, map->p) + 1;
+ nentries = 0;
+ for (i = 0; i < MAGIC_SETS; i++) {
+ if (needsbyteswap)
+ map->nmagic[i] = swap4(ptr[i + 2]);
+ else
+ map->nmagic[i] = ptr[i + 2];
+ if (i != MAGIC_SETS - 1)
+ map->magic[i + 1] = map->magic[i] + map->nmagic[i];
+ nentries += map->nmagic[i];
+ }
+ if (entries != nentries + 1) {
+ file_error(ms, 0, "Inconsistent entries in `%s' %u != %u",
+ dbname, entries, nentries + 1);
+ return -1;
+ }
+ if (needsbyteswap)
+ for (i = 0; i < MAGIC_SETS; i++)
+ byteswap(map->magic[i], map->nmagic[i]);
+ return 0;
+}
+
+/*
+ * handle an mmaped file.
+ */
+file_private int
+apprentice_compile(struct magic_set *ms, struct magic_map *map, const char *fn)
+{
+ static const size_t nm = sizeof(*map->nmagic) * MAGIC_SETS;
+ static const size_t m = sizeof(**map->magic);
+ int fd = -1;
+ size_t len;
+ char *dbname;
+ int rv = -1;
+ uint32_t i;
+ union {
+ struct magic m;
+ uint32_t h[2 + MAGIC_SETS];
+ } hdr;
+
+ dbname = mkdbname(ms, fn, 1);
+
+ if (dbname == NULL)
+ goto out;
+
+ if ((fd = open(dbname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0644)) == -1)
+ {
+ file_error(ms, errno, "cannot open `%s'", dbname);
+ goto out;
+ }
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.h[0] = MAGICNO;
+ hdr.h[1] = VERSIONNO;
+ memcpy(hdr.h + 2, map->nmagic, nm);
+
+ if (write(fd, &hdr, sizeof(hdr)) != CAST(ssize_t, sizeof(hdr))) {
+ file_error(ms, errno, "error writing `%s'", dbname);
+ goto out2;
+ }
+
+ for (i = 0; i < MAGIC_SETS; i++) {
+ len = m * map->nmagic[i];
+ if (write(fd, map->magic[i], len) != CAST(ssize_t, len)) {
+ file_error(ms, errno, "error writing `%s'", dbname);
+ goto out2;
+ }
+ }
+
+ rv = 0;
+out2:
+ if (fd != -1)
+ (void)close(fd);
+out:
+ apprentice_unmap(map);
+ free(dbname);
+ return rv;
+}
+
+file_private const char ext[] = ".mgc";
+/*
+ * make a dbname
+ */
+file_private char *
+mkdbname(struct magic_set *ms, const char *fn, int strip)
+{
+ const char *p, *q;
+ char *buf;
+
+ if (strip) {
+ if ((p = strrchr(fn, '/')) != NULL)
+ fn = ++p;
+ }
+
+ for (q = fn; *q; q++)
+ continue;
+ /* Look for .mgc */
+ for (p = ext + sizeof(ext) - 1; p >= ext && q >= fn; p--, q--)
+ if (*p != *q)
+ break;
+
+ /* Did not find .mgc, restore q */
+ if (p >= ext)
+ while (*q)
+ q++;
+
+ q++;
+ /* Compatibility with old code that looked in .mime */
+ if (ms->flags & MAGIC_MIME) {
+ if (asprintf(&buf, "%.*s.mime%s", CAST(int, q - fn), fn, ext)
+ < 0)
+ return NULL;
+ if (access(buf, R_OK) != -1) {
+ ms->flags &= MAGIC_MIME_TYPE;
+ return buf;
+ }
+ free(buf);
+ }
+ if (asprintf(&buf, "%.*s%s", CAST(int, q - fn), fn, ext) < 0)
+ return NULL;
+
+ /* Compatibility with old code that looked in .mime */
+ if (strstr(fn, ".mime") != NULL)
+ ms->flags &= MAGIC_MIME_TYPE;
+ return buf;
+}
+
+/*
+ * Byteswap an mmap'ed file if needed
+ */
+file_private void
+byteswap(struct magic *magic, uint32_t nmagic)
+{
+ uint32_t i;
+ for (i = 0; i < nmagic; i++)
+ bs1(&magic[i]);
+}
+
+#if !defined(HAVE_BYTESWAP_H) && !defined(HAVE_SYS_BSWAP_H)
+/*
+ * swap a short
+ */
+file_private uint16_t
+swap2(uint16_t sv)
+{
+ uint16_t rv;
+ uint8_t *s = RCAST(uint8_t *, RCAST(void *, &sv));
+ uint8_t *d = RCAST(uint8_t *, RCAST(void *, &rv));
+ d[0] = s[1];
+ d[1] = s[0];
+ return rv;
+}
+
+/*
+ * swap an int
+ */
+file_private uint32_t
+swap4(uint32_t sv)
+{
+ uint32_t rv;
+ uint8_t *s = RCAST(uint8_t *, RCAST(void *, &sv));
+ uint8_t *d = RCAST(uint8_t *, RCAST(void *, &rv));
+ d[0] = s[3];
+ d[1] = s[2];
+ d[2] = s[1];
+ d[3] = s[0];
+ return rv;
+}
+
+/*
+ * swap a quad
+ */
+file_private uint64_t
+swap8(uint64_t sv)
+{
+ uint64_t rv;
+ uint8_t *s = RCAST(uint8_t *, RCAST(void *, &sv));
+ uint8_t *d = RCAST(uint8_t *, RCAST(void *, &rv));
+# if 0
+ d[0] = s[3];
+ d[1] = s[2];
+ d[2] = s[1];
+ d[3] = s[0];
+ d[4] = s[7];
+ d[5] = s[6];
+ d[6] = s[5];
+ d[7] = s[4];
+# else
+ d[0] = s[7];
+ d[1] = s[6];
+ d[2] = s[5];
+ d[3] = s[4];
+ d[4] = s[3];
+ d[5] = s[2];
+ d[6] = s[1];
+ d[7] = s[0];
+# endif
+ return rv;
+}
+#endif
+
+file_protected uintmax_t
+file_varint2uintmax_t(const unsigned char *us, int t, size_t *l)
+{
+ uintmax_t x = 0;
+ const unsigned char *c;
+ if (t == FILE_LEVARINT) {
+ for (c = us; *c; c++) {
+ if ((*c & 0x80) == 0)
+ break;
+ }
+ if (l)
+ *l = c - us + 1;
+ for (; c >= us; c--) {
+ x |= *c & 0x7f;
+ x <<= 7;
+ }
+ } else {
+ for (c = us; *c; c++) {
+ x |= *c & 0x7f;
+ if ((*c & 0x80) == 0)
+ break;
+ x <<= 7;
+ }
+ if (l)
+ *l = c - us + 1;
+ }
+ return x;
+}
+
+
+/*
+ * byteswap a single magic entry
+ */
+file_private void
+bs1(struct magic *m)
+{
+ m->cont_level = swap2(m->cont_level);
+ m->offset = swap4(CAST(uint32_t, m->offset));
+ m->in_offset = swap4(CAST(uint32_t, m->in_offset));
+ m->lineno = swap4(CAST(uint32_t, m->lineno));
+ if (IS_STRING(m->type)) {
+ m->str_range = swap4(m->str_range);
+ m->str_flags = swap4(m->str_flags);
+ }
+ else {
+ m->value.q = swap8(m->value.q);
+ m->num_mask = swap8(m->num_mask);
+ }
+}
+
+file_protected size_t
+file_pstring_length_size(struct magic_set *ms, const struct magic *m)
+{
+ switch (m->str_flags & PSTRING_LEN) {
+ case PSTRING_1_LE:
+ return 1;
+ case PSTRING_2_LE:
+ case PSTRING_2_BE:
+ return 2;
+ case PSTRING_4_LE:
+ case PSTRING_4_BE:
+ return 4;
+ default:
+ file_error(ms, 0, "corrupt magic file "
+ "(bad pascal string length %d)",
+ m->str_flags & PSTRING_LEN);
+ return FILE_BADSIZE;
+ }
+}
+file_protected size_t
+file_pstring_get_length(struct magic_set *ms, const struct magic *m,
+ const char *ss)
+{
+ size_t len = 0;
+ const unsigned char *s = RCAST(const unsigned char *, ss);
+ unsigned int s3, s2, s1, s0;
+
+ switch (m->str_flags & PSTRING_LEN) {
+ case PSTRING_1_LE:
+ len = *s;
+ break;
+ case PSTRING_2_LE:
+ s0 = s[0];
+ s1 = s[1];
+ len = (s1 << 8) | s0;
+ break;
+ case PSTRING_2_BE:
+ s0 = s[0];
+ s1 = s[1];
+ len = (s0 << 8) | s1;
+ break;
+ case PSTRING_4_LE:
+ s0 = s[0];
+ s1 = s[1];
+ s2 = s[2];
+ s3 = s[3];
+ len = (s3 << 24) | (s2 << 16) | (s1 << 8) | s0;
+ break;
+ case PSTRING_4_BE:
+ s0 = s[0];
+ s1 = s[1];
+ s2 = s[2];
+ s3 = s[3];
+ len = (s0 << 24) | (s1 << 16) | (s2 << 8) | s3;
+ break;
+ default:
+ file_error(ms, 0, "corrupt magic file "
+ "(bad pascal string length %d)",
+ m->str_flags & PSTRING_LEN);
+ return FILE_BADSIZE;
+ }
+
+ if (m->str_flags & PSTRING_LENGTH_INCLUDES_ITSELF) {
+ size_t l = file_pstring_length_size(ms, m);
+ if (l == FILE_BADSIZE)
+ return l;
+ len -= l;
+ }
+
+ return len;
+}
+
+file_protected int
+file_magicfind(struct magic_set *ms, const char *name, struct mlist *v)
+{
+ uint32_t i, j;
+ struct mlist *mlist, *ml;
+
+ mlist = ms->mlist[1];
+
+ for (ml = mlist->next; ml != mlist; ml = ml->next) {
+ struct magic *ma = ml->magic;
+ for (i = 0; i < ml->nmagic; i++) {
+ if (ma[i].type != FILE_NAME)
+ continue;
+ if (strcmp(ma[i].value.s, name) == 0) {
+ v->magic = &ma[i];
+ v->magic_rxcomp = &(ml->magic_rxcomp[i]);
+ for (j = i + 1; j < ml->nmagic; j++)
+ if (ma[j].cont_level == 0)
+ break;
+ v->nmagic = j - i;
+ return 0;
+ }
+ }
+ }
+ return -1;
+}
diff --git a/src/apptype.c b/src/apptype.c
new file mode 100644
index 0000000..9473627
--- /dev/null
+++ b/src/apptype.c
@@ -0,0 +1,169 @@
+/*
+ * Adapted from: apptype.c, Written by Eberhard Mattes and put into the
+ * file_public domain
+ *
+ * Notes: 1. Qualify the filename so that DosQueryAppType does not do extraneous
+ * searches.
+ *
+ * 2. DosQueryAppType will return FAPPTYP_DOS on a file ending with ".com"
+ * (other than an OS/2 exe or Win exe with this name). Eberhard Mattes
+ * remarks Tue, 6 Apr 93: Moreover, it reports the type of the (new and very
+ * bug ridden) Win Emacs as "OS/2 executable".
+ *
+ * 3. apptype() uses the filename if given, otherwise a tmp file is created with
+ * the contents of buf. If buf is not the complete file, apptype can
+ * incorrectly identify the exe type. The "-z" option of "file" is the reason
+ * for this ugly code.
+ */
+
+/*
+ * amai: Darrel Hankerson did the changes described here.
+ *
+ * It remains to check the validity of comments (2.) since it's referred to an
+ * "old" OS/2 version.
+ *
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: apptype.c,v 1.17 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __EMX__
+#include <io.h>
+#define INCL_DOSSESMGR
+#define INCL_DOSERRORS
+#define INCL_DOSFILEMGR
+#include <os2.h>
+typedef ULONG APPTYPE;
+
+file_protected int
+file_os2_apptype(struct magic_set *ms, const char *fn, const void *buf,
+ size_t nb)
+{
+ APPTYPE rc, type;
+ char path[_MAX_PATH], drive[_MAX_DRIVE], dir[_MAX_DIR],
+ fname[_MAX_FNAME], ext[_MAX_EXT];
+ char *filename;
+ FILE *fp;
+
+ if (fn)
+ filename = strdup(fn);
+ else if ((filename = tempnam("./", "tmp")) == NULL) {
+ file_error(ms, errno, "cannot create tempnam");
+ return -1;
+ }
+ /* qualify the filename to prevent extraneous searches */
+ _splitpath(filename, drive, dir, fname, ext);
+ (void)sprintf(path, "%s%s%s%s", drive,
+ (*dir == '\0') ? "./" : dir,
+ fname,
+ (*ext == '\0') ? "." : ext);
+
+ if (fn == NULL) {
+ if ((fp = fopen(path, "wb")) == NULL) {
+ file_error(ms, errno, "cannot open tmp file `%s'", path);
+ return -1;
+ }
+ if (fwrite(buf, 1, nb, fp) != nb) {
+ file_error(ms, errno, "cannot write tmp file `%s'",
+ path);
+ (void)fclose(fp);
+ return -1;
+ }
+ (void)fclose(fp);
+ }
+ rc = DosQueryAppType((unsigned char *)path, &type);
+
+ if (fn == NULL) {
+ unlink(path);
+ free(filename);
+ }
+#if 0
+ if (rc == ERROR_INVALID_EXE_SIGNATURE)
+ printf("%s: not an executable file\n", fname);
+ else if (rc == ERROR_FILE_NOT_FOUND)
+ printf("%s: not found\n", fname);
+ else if (rc == ERROR_ACCESS_DENIED)
+ printf("%s: access denied\n", fname);
+ else if (rc != 0)
+ printf("%s: error code = %lu\n", fname, rc);
+ else
+#else
+
+ /*
+ * for our purpose here it's sufficient to just ignore the error and
+ * return w/o success (=0)
+ */
+
+ if (rc)
+ return (0);
+
+#endif
+
+ if (type & FAPPTYP_32BIT)
+ if (file_printf(ms, "32-bit ") == -1)
+ return -1;
+ if (type & FAPPTYP_PHYSDRV) {
+ if (file_printf(ms, "physical device driver") == -1)
+ return -1;
+ } else if (type & FAPPTYP_VIRTDRV) {
+ if (file_printf(ms, "virtual device driver") == -1)
+ return -1;
+ } else if (type & FAPPTYP_DLL) {
+ if (type & FAPPTYP_PROTDLL)
+ if (file_printf(ms, "file_protected ") == -1)
+ return -1;
+ if (file_printf(ms, "DLL") == -1)
+ return -1;
+ } else if (type & (FAPPTYP_WINDOWSREAL | FAPPTYP_WINDOWSPROT)) {
+ if (file_printf(ms, "Windows executable") == -1)
+ return -1;
+ } else if (type & FAPPTYP_DOS) {
+ /*
+ * The API routine is partially broken on filenames ending
+ * ".com".
+ */
+ if (stricmp(ext, ".com") == 0)
+ if (strncmp((const char *)buf, "MZ", 2))
+ return (0);
+ if (file_printf(ms, "DOS executable") == -1)
+ return -1;
+ /* ---------------------------------------- */
+ /* Might learn more from the magic(4) entry */
+ if (file_printf(ms, ", magic(4)-> ") == -1)
+ return -1;
+ return (0);
+ /* ---------------------------------------- */
+ } else if (type & FAPPTYP_BOUND) {
+ if (file_printf(ms, "bound executable") == -1)
+ return -1;
+ } else if ((type & 7) == FAPPTYP_WINDOWAPI) {
+ if (file_printf(ms, "PM executable") == -1)
+ return -1;
+ } else if (file_printf(ms, "OS/2 executable") == -1)
+ return -1;
+
+ switch (type & (FAPPTYP_NOTWINDOWCOMPAT |
+ FAPPTYP_WINDOWCOMPAT |
+ FAPPTYP_WINDOWAPI)) {
+ case FAPPTYP_NOTWINDOWCOMPAT:
+ if (file_printf(ms, " [NOTWINDOWCOMPAT]") == -1)
+ return -1;
+ break;
+ case FAPPTYP_WINDOWCOMPAT:
+ if (file_printf(ms, " [WINDOWCOMPAT]") == -1)
+ return -1;
+ break;
+ case FAPPTYP_WINDOWAPI:
+ if (file_printf(ms, " [WINDOWAPI]") == -1)
+ return -1;
+ break;
+ }
+ return 1;
+}
+#endif
diff --git a/src/ascmagic.c b/src/ascmagic.c
new file mode 100644
index 0000000..487f55d
--- /dev/null
+++ b/src/ascmagic.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * ASCII magic -- try to detect text encoding.
+ *
+ * Extensively modified by Eric Fischer <enf@pobox.com> in July, 2000,
+ * to handle character codes other than ASCII on a unified basis.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: ascmagic.c,v 1.113 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#define MAXLINELEN 300 /* longest sane line length */
+#define ISSPC(x) ((x) == ' ' || (x) == '\t' || (x) == '\r' || (x) == '\n' \
+ || (x) == 0x85 || (x) == '\f')
+
+file_private unsigned char *encode_utf8(unsigned char *, size_t, file_unichar_t *,
+ size_t);
+file_private size_t trim_nuls(const unsigned char *, size_t);
+
+/*
+ * Undo the NUL-termination kindly provided by process()
+ * but leave at least one byte to look at
+ */
+file_private size_t
+trim_nuls(const unsigned char *buf, size_t nbytes)
+{
+ while (nbytes > 1 && buf[nbytes - 1] == '\0')
+ nbytes--;
+
+ return nbytes;
+}
+
+file_protected int
+file_ascmagic(struct magic_set *ms, const struct buffer *b, int text)
+{
+ file_unichar_t *ubuf = NULL;
+ size_t ulen = 0;
+ int rv = 1;
+ struct buffer bb;
+
+ const char *code = NULL;
+ const char *code_mime = NULL;
+ const char *type = NULL;
+
+ bb = *b;
+ bb.flen = trim_nuls(CAST(const unsigned char *, b->fbuf), b->flen);
+ /*
+ * Avoid trimming at an odd byte if the original buffer was evenly
+ * sized; this avoids losing the last character on UTF-16 LE text
+ */
+ if ((bb.flen & 1) && !(b->flen & 1))
+ bb.flen++;
+
+ /* If file doesn't look like any sort of text, give up. */
+ if (file_encoding(ms, &bb, &ubuf, &ulen, &code, &code_mime,
+ &type) == 0)
+ rv = 0;
+ else
+ rv = file_ascmagic_with_encoding(ms, &bb,
+ ubuf, ulen, code, type, text);
+
+ free(ubuf);
+
+ return rv;
+}
+
+file_protected int
+file_ascmagic_with_encoding(struct magic_set *ms, const struct buffer *b,
+ file_unichar_t *ubuf, size_t ulen, const char *code, const char *type,
+ int text)
+{
+ struct buffer bb;
+ const unsigned char *buf = CAST(const unsigned char *, b->fbuf);
+ size_t nbytes = b->flen;
+ unsigned char *utf8_buf = NULL, *utf8_end;
+ size_t mlen, i, len;
+ int rv = -1;
+ int mime = ms->flags & MAGIC_MIME;
+ int need_separator = 0;
+
+ const char *subtype = NULL;
+
+ int has_escapes = 0;
+ int has_backspace = 0;
+ int seen_cr = 0;
+
+ int n_crlf = 0;
+ int n_lf = 0;
+ int n_cr = 0;
+ int n_nel = 0;
+ int executable = 0;
+
+ size_t last_line_end = CAST(size_t, -1);
+ size_t has_long_lines = 0;
+
+ nbytes = trim_nuls(buf, nbytes);
+
+ /* If we have fewer than 2 bytes, give up. */
+ if (nbytes <= 1) {
+ rv = 0;
+ goto done;
+ }
+
+ if (ulen > 0 && (ms->flags & MAGIC_NO_CHECK_SOFT) == 0) {
+ /* Convert ubuf to UTF-8 and try text soft magic */
+ /* malloc size is a conservative overestimate; could be
+ improved, or at least realloced after conversion. */
+ mlen = ulen * 6;
+ if ((utf8_buf = CAST(unsigned char *, malloc(mlen))) == NULL) {
+ file_oomem(ms, mlen);
+ goto done;
+ }
+ if ((utf8_end = encode_utf8(utf8_buf, mlen, ubuf, ulen))
+ == NULL)
+ goto done;
+ buffer_init(&bb, b->fd, &b->st, utf8_buf,
+ CAST(size_t, utf8_end - utf8_buf));
+
+ if ((rv = file_softmagic(ms, &bb, NULL, NULL,
+ TEXTTEST, text)) == 0)
+ rv = -1;
+ else
+ need_separator = 1;
+ buffer_fini(&bb);
+ if ((ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION))) {
+ rv = rv == -1 ? 0 : 1;
+ goto done;
+ }
+ }
+
+ if ((ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION))) {
+ rv = 0;
+ goto done;
+ }
+
+ /* Now try to discover other details about the file. */
+ for (i = 0; i < ulen; i++) {
+ if (ubuf[i] == '\n') {
+ if (seen_cr)
+ n_crlf++;
+ else
+ n_lf++;
+ last_line_end = i;
+ } else if (seen_cr)
+ n_cr++;
+
+ seen_cr = (ubuf[i] == '\r');
+ if (seen_cr)
+ last_line_end = i;
+
+ if (ubuf[i] == 0x85) { /* X3.64/ECMA-43 "next line" character */
+ n_nel++;
+ last_line_end = i;
+ }
+
+ /* If this line is _longer_ than MAXLINELEN, remember it. */
+ if (i > last_line_end + MAXLINELEN) {
+ size_t ll = i - last_line_end;
+ if (ll > has_long_lines)
+ has_long_lines = ll;
+ }
+
+ if (ubuf[i] == '\033')
+ has_escapes = 1;
+ if (ubuf[i] == '\b')
+ has_backspace = 1;
+ }
+
+ /* Beware, if the data has been truncated, the final CR could have
+ been followed by a LF. If we have ms->bytes_max bytes, it indicates
+ that the data might have been truncated, probably even before
+ this function was called. */
+ if (seen_cr && nbytes < ms->bytes_max)
+ n_cr++;
+
+ if (strcmp(type, "binary") == 0) {
+ rv = 0;
+ goto done;
+ }
+ len = file_printedlen(ms);
+ if (mime) {
+ if ((mime & MAGIC_MIME_TYPE) != 0) {
+ if (len) {
+ /*
+ * Softmagic printed something, we
+ * are either done, or we need a separator
+ */
+ if ((ms->flags & MAGIC_CONTINUE) == 0) {
+ rv = 1;
+ goto done;
+ }
+ if (need_separator && file_separator(ms) == -1)
+ goto done;
+ } else {
+ if (file_printf(ms, "text/plain") == -1)
+ goto done;
+ }
+ }
+ } else {
+ if (len) {
+ switch (file_replace(ms, " text$", ", ")) {
+ case 0:
+ switch (file_replace(ms, " text executable$",
+ ", ")) {
+ case 0:
+ if (file_printf(ms, ", ") == -1)
+ goto done;
+ break;
+ case -1:
+ goto done;
+ default:
+ executable = 1;
+ break;
+ }
+ break;
+ case -1:
+ goto done;
+ default:
+ break;
+ }
+ }
+
+ if (file_printf(ms, "%s", code) == -1)
+ goto done;
+
+ if (subtype) {
+ if (file_printf(ms, " %s", subtype) == -1)
+ goto done;
+ }
+
+ if (file_printf(ms, " %s", type) == -1)
+ goto done;
+
+ if (executable)
+ if (file_printf(ms, " executable") == -1)
+ goto done;
+
+ if (has_long_lines)
+ if (file_printf(ms, ", with very long lines (%"
+ SIZE_T_FORMAT "u)", has_long_lines) == -1)
+ goto done;
+
+ /*
+ * Only report line terminators if we find one other than LF,
+ * or if we find none at all.
+ */
+ if ((n_crlf == 0 && n_cr == 0 && n_nel == 0 && n_lf == 0) ||
+ (n_crlf != 0 || n_cr != 0 || n_nel != 0)) {
+ if (file_printf(ms, ", with") == -1)
+ goto done;
+
+ if (n_crlf == 0 && n_cr == 0 &&
+ n_nel == 0 && n_lf == 0) {
+ if (file_printf(ms, " no") == -1)
+ goto done;
+ } else {
+ if (n_crlf) {
+ if (file_printf(ms, " CRLF") == -1)
+ goto done;
+ if (n_cr || n_lf || n_nel)
+ if (file_printf(ms, ",") == -1)
+ goto done;
+ }
+ if (n_cr) {
+ if (file_printf(ms, " CR") == -1)
+ goto done;
+ if (n_lf || n_nel)
+ if (file_printf(ms, ",") == -1)
+ goto done;
+ }
+ if (n_lf) {
+ if (file_printf(ms, " LF") == -1)
+ goto done;
+ if (n_nel)
+ if (file_printf(ms, ",") == -1)
+ goto done;
+ }
+ if (n_nel)
+ if (file_printf(ms, " NEL") == -1)
+ goto done;
+ }
+
+ if (file_printf(ms, " line terminators") == -1)
+ goto done;
+ }
+
+ if (has_escapes)
+ if (file_printf(ms, ", with escape sequences") == -1)
+ goto done;
+ if (has_backspace)
+ if (file_printf(ms, ", with overstriking") == -1)
+ goto done;
+ }
+ rv = 1;
+done:
+ free(utf8_buf);
+
+ return rv;
+}
+
+/*
+ * Encode Unicode string as UTF-8, returning pointer to character
+ * after end of string, or NULL if an invalid character is found.
+ */
+file_private unsigned char *
+encode_utf8(unsigned char *buf, size_t len, file_unichar_t *ubuf, size_t ulen)
+{
+ size_t i;
+ unsigned char *end = buf + len;
+
+ for (i = 0; i < ulen; i++) {
+ if (ubuf[i] <= 0x7f) {
+ if (end - buf < 1)
+ return NULL;
+ *buf++ = CAST(unsigned char, ubuf[i]);
+ continue;
+ }
+ if (ubuf[i] <= 0x7ff) {
+ if (end - buf < 2)
+ return NULL;
+ *buf++ = CAST(unsigned char, (ubuf[i] >> 6) + 0xc0);
+ goto out1;
+ }
+ if (ubuf[i] <= 0xffff) {
+ if (end - buf < 3)
+ return NULL;
+ *buf++ = CAST(unsigned char, (ubuf[i] >> 12) + 0xe0);
+ goto out2;
+ }
+ if (ubuf[i] <= 0x1fffff) {
+ if (end - buf < 4)
+ return NULL;
+ *buf++ = CAST(unsigned char, (ubuf[i] >> 18) + 0xf0);
+ goto out3;
+ }
+ if (ubuf[i] <= 0x3ffffff) {
+ if (end - buf < 5)
+ return NULL;
+ *buf++ = CAST(unsigned char, (ubuf[i] >> 24) + 0xf8);
+ goto out4;
+ }
+ if (ubuf[i] <= 0x7fffffff) {
+ if (end - buf < 6)
+ return NULL;
+ *buf++ = CAST(unsigned char, (ubuf[i] >> 30) + 0xfc);
+ goto out5;
+ }
+ /* Invalid character */
+ return NULL;
+ out5: *buf++ = CAST(unsigned char, ((ubuf[i] >> 24) & 0x3f) + 0x80);
+ out4: *buf++ = CAST(unsigned char, ((ubuf[i] >> 18) & 0x3f) + 0x80);
+ out3: *buf++ = CAST(unsigned char, ((ubuf[i] >> 12) & 0x3f) + 0x80);
+ out2: *buf++ = CAST(unsigned char, ((ubuf[i] >> 6) & 0x3f) + 0x80);
+ out1: *buf++ = CAST(unsigned char, ((ubuf[i] >> 0) & 0x3f) + 0x80);
+ }
+
+ return buf;
+}
diff --git a/src/asctime_r.c b/src/asctime_r.c
new file mode 100644
index 0000000..c76ca69
--- /dev/null
+++ b/src/asctime_r.c
@@ -0,0 +1,19 @@
+/* $File: asctime_r.c,v 1.3 2022/09/24 20:30:13 christos Exp $ */
+
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: asctime_r.c,v 1.3 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+#include <time.h>
+#include <string.h>
+
+/* asctime_r is not thread-safe anyway */
+char *
+asctime_r(const struct tm *t, char *dst)
+{
+ char *p = asctime(t);
+ if (p == NULL)
+ return NULL;
+ memcpy(dst, p, 26);
+ return dst;
+}
diff --git a/src/asprintf.c b/src/asprintf.c
new file mode 100644
index 0000000..ae84ab9
--- /dev/null
+++ b/src/asprintf.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: asprintf.c,v 1.7 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+int asprintf(char **ptr, const char *fmt, ...)
+{
+ va_list vargs;
+ int retval;
+
+ va_start(vargs, fmt);
+ retval = vasprintf(ptr, fmt, vargs);
+ va_end(vargs);
+
+ return retval;
+}
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 0000000..ac9fd15
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) Christos Zoulas 2017.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: buffer.c,v 1.10 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+void
+buffer_init(struct buffer *b, int fd, const struct stat *st, const void *data,
+ size_t len)
+{
+ b->fd = fd;
+ if (st)
+ memcpy(&b->st, st, sizeof(b->st));
+ else if (b->fd == -1 || fstat(b->fd, &b->st) == -1)
+ memset(&b->st, 0, sizeof(b->st));
+ b->fbuf = data;
+ b->flen = len;
+ b->eoff = 0;
+ b->ebuf = NULL;
+ b->elen = 0;
+}
+
+void
+buffer_fini(struct buffer *b)
+{
+ free(b->ebuf);
+}
+
+int
+buffer_fill(const struct buffer *bb)
+{
+ struct buffer *b = CCAST(struct buffer *, bb);
+
+ if (b->elen != 0)
+ return b->elen == FILE_BADSIZE ? -1 : 0;
+
+ if (!S_ISREG(b->st.st_mode))
+ goto out;
+
+ b->elen = CAST(size_t, b->st.st_size) < b->flen ?
+ CAST(size_t, b->st.st_size) : b->flen;
+ if ((b->ebuf = malloc(b->elen)) == NULL)
+ goto out;
+
+ b->eoff = b->st.st_size - b->elen;
+ if (pread(b->fd, b->ebuf, b->elen, b->eoff) == -1) {
+ free(b->ebuf);
+ b->ebuf = NULL;
+ goto out;
+ }
+
+ return 0;
+out:
+ b->elen = FILE_BADSIZE;
+ return -1;
+}
diff --git a/src/cdf.c b/src/cdf.c
new file mode 100644
index 0000000..36a6031
--- /dev/null
+++ b/src/cdf.c
@@ -0,0 +1,1676 @@
+/*-
+ * Copyright (c) 2008 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+/*
+ * Parse Composite Document Files, the format used in Microsoft Office
+ * document files before they switched to zipped XML.
+ * Info from: http://sc.openoffice.org/compdocfileformat.pdf
+ *
+ * N.B. This is the "Composite Document File" format, and not the
+ * "Compound Document Format", nor the "Channel Definition Format".
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: cdf.c,v 1.123 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+#include <assert.h>
+#ifdef CDF_DEBUG
+#include <err.h>
+#endif
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+#include <limits.h>
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifdef HAVE_SYS_BSWAP_H
+#include <sys/bswap.h>
+#endif
+
+#ifndef EFTYPE
+#define EFTYPE EINVAL
+#endif
+
+#ifndef SIZE_T_MAX
+#define SIZE_T_MAX CAST(size_t, ~0ULL)
+#endif
+
+#include "cdf.h"
+
+#ifdef CDF_DEBUG
+#define DPRINTF(a) printf a, fflush(stdout)
+#else
+#define DPRINTF(a)
+#endif
+
+static union {
+ char s[4];
+ uint32_t u;
+} cdf_bo;
+
+#define NEED_SWAP (cdf_bo.u == CAST(uint32_t, 0x01020304))
+
+#define CDF_TOLE8(x) \
+ (CAST(uint64_t, NEED_SWAP ? _cdf_tole8(x) : CAST(uint64_t, x)))
+#define CDF_TOLE4(x) \
+ (CAST(uint32_t, NEED_SWAP ? _cdf_tole4(x) : CAST(uint32_t, x)))
+#define CDF_TOLE2(x) \
+ (CAST(uint16_t, NEED_SWAP ? _cdf_tole2(x) : CAST(uint16_t, x)))
+#define CDF_TOLE(x) (/*CONSTCOND*/sizeof(x) == 2 ? \
+ CDF_TOLE2(CAST(uint16_t, x)) : \
+ (/*CONSTCOND*/sizeof(x) == 4 ? \
+ CDF_TOLE4(CAST(uint32_t, x)) : \
+ CDF_TOLE8(CAST(uint64_t, x))))
+#define CDF_GETUINT32(x, y) cdf_getuint32(x, y)
+
+#define CDF_MALLOC(n) cdf_malloc(__FILE__, __LINE__, (n))
+#define CDF_REALLOC(p, n) cdf_realloc(__FILE__, __LINE__, (p), (n))
+#define CDF_CALLOC(n, u) cdf_calloc(__FILE__, __LINE__, (n), (u))
+
+
+/*ARGSUSED*/
+static void *
+cdf_malloc(const char *file __attribute__((__unused__)),
+ size_t line __attribute__((__unused__)), size_t n)
+{
+ DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
+ file, line, __func__, n));
+ if (n == 0)
+ n++;
+ return malloc(n);
+}
+
+/*ARGSUSED*/
+static void *
+cdf_realloc(const char *file __attribute__((__unused__)),
+ size_t line __attribute__((__unused__)), void *p, size_t n)
+{
+ DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
+ file, line, __func__, n));
+ return realloc(p, n);
+}
+
+/*ARGSUSED*/
+static void *
+cdf_calloc(const char *file __attribute__((__unused__)),
+ size_t line __attribute__((__unused__)), size_t n, size_t u)
+{
+ DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u %"
+ SIZE_T_FORMAT "u\n", file, line, __func__, n, u));
+ if (n == 0)
+ n++;
+ return calloc(n, u);
+}
+
+#if defined(HAVE_BYTESWAP_H)
+# define _cdf_tole2(x) bswap_16(x)
+# define _cdf_tole4(x) bswap_32(x)
+# define _cdf_tole8(x) bswap_64(x)
+#elif defined(HAVE_SYS_BSWAP_H)
+# define _cdf_tole2(x) bswap16(x)
+# define _cdf_tole4(x) bswap32(x)
+# define _cdf_tole8(x) bswap64(x)
+#else
+/*
+ * swap a short
+ */
+static uint16_t
+_cdf_tole2(uint16_t sv)
+{
+ uint16_t rv;
+ uint8_t *s = RCAST(uint8_t *, RCAST(void *, &sv));
+ uint8_t *d = RCAST(uint8_t *, RCAST(void *, &rv));
+ d[0] = s[1];
+ d[1] = s[0];
+ return rv;
+}
+
+/*
+ * swap an int
+ */
+static uint32_t
+_cdf_tole4(uint32_t sv)
+{
+ uint32_t rv;
+ uint8_t *s = RCAST(uint8_t *, RCAST(void *, &sv));
+ uint8_t *d = RCAST(uint8_t *, RCAST(void *, &rv));
+ d[0] = s[3];
+ d[1] = s[2];
+ d[2] = s[1];
+ d[3] = s[0];
+ return rv;
+}
+
+/*
+ * swap a quad
+ */
+static uint64_t
+_cdf_tole8(uint64_t sv)
+{
+ uint64_t rv;
+ uint8_t *s = RCAST(uint8_t *, RCAST(void *, &sv));
+ uint8_t *d = RCAST(uint8_t *, RCAST(void *, &rv));
+ d[0] = s[7];
+ d[1] = s[6];
+ d[2] = s[5];
+ d[3] = s[4];
+ d[4] = s[3];
+ d[5] = s[2];
+ d[6] = s[1];
+ d[7] = s[0];
+ return rv;
+}
+#endif
+
+/*
+ * grab a uint32_t from a possibly unaligned address, and return it in
+ * the native host order.
+ */
+static uint32_t
+cdf_getuint32(const uint8_t *p, size_t offs)
+{
+ uint32_t rv;
+ (void)memcpy(&rv, p + offs * sizeof(uint32_t), sizeof(rv));
+ return CDF_TOLE4(rv);
+}
+
+#define CDF_UNPACK(a) \
+ (void)memcpy(&(a), &buf[len], sizeof(a)), len += sizeof(a)
+#define CDF_UNPACKA(a) \
+ (void)memcpy((a), &buf[len], sizeof(a)), len += sizeof(a)
+
+uint16_t
+cdf_tole2(uint16_t sv)
+{
+ return CDF_TOLE2(sv);
+}
+
+uint32_t
+cdf_tole4(uint32_t sv)
+{
+ return CDF_TOLE4(sv);
+}
+
+uint64_t
+cdf_tole8(uint64_t sv)
+{
+ return CDF_TOLE8(sv);
+}
+
+void
+cdf_swap_header(cdf_header_t *h)
+{
+ size_t i;
+
+ h->h_magic = CDF_TOLE8(h->h_magic);
+ h->h_uuid[0] = CDF_TOLE8(h->h_uuid[0]);
+ h->h_uuid[1] = CDF_TOLE8(h->h_uuid[1]);
+ h->h_revision = CDF_TOLE2(h->h_revision);
+ h->h_version = CDF_TOLE2(h->h_version);
+ h->h_byte_order = CDF_TOLE2(h->h_byte_order);
+ h->h_sec_size_p2 = CDF_TOLE2(h->h_sec_size_p2);
+ h->h_short_sec_size_p2 = CDF_TOLE2(h->h_short_sec_size_p2);
+ h->h_num_sectors_in_sat = CDF_TOLE4(h->h_num_sectors_in_sat);
+ h->h_secid_first_directory = CDF_TOLE4(h->h_secid_first_directory);
+ h->h_min_size_standard_stream =
+ CDF_TOLE4(h->h_min_size_standard_stream);
+ h->h_secid_first_sector_in_short_sat =
+ CDF_TOLE4(CAST(uint32_t, h->h_secid_first_sector_in_short_sat));
+ h->h_num_sectors_in_short_sat =
+ CDF_TOLE4(h->h_num_sectors_in_short_sat);
+ h->h_secid_first_sector_in_master_sat =
+ CDF_TOLE4(CAST(uint32_t, h->h_secid_first_sector_in_master_sat));
+ h->h_num_sectors_in_master_sat =
+ CDF_TOLE4(h->h_num_sectors_in_master_sat);
+ for (i = 0; i < __arraycount(h->h_master_sat); i++) {
+ h->h_master_sat[i] =
+ CDF_TOLE4(CAST(uint32_t, h->h_master_sat[i]));
+ }
+}
+
+void
+cdf_unpack_header(cdf_header_t *h, char *buf)
+{
+ size_t i;
+ size_t len = 0;
+
+ CDF_UNPACK(h->h_magic);
+ CDF_UNPACKA(h->h_uuid);
+ CDF_UNPACK(h->h_revision);
+ CDF_UNPACK(h->h_version);
+ CDF_UNPACK(h->h_byte_order);
+ CDF_UNPACK(h->h_sec_size_p2);
+ CDF_UNPACK(h->h_short_sec_size_p2);
+ CDF_UNPACKA(h->h_unused0);
+ CDF_UNPACK(h->h_num_sectors_in_sat);
+ CDF_UNPACK(h->h_secid_first_directory);
+ CDF_UNPACKA(h->h_unused1);
+ CDF_UNPACK(h->h_min_size_standard_stream);
+ CDF_UNPACK(h->h_secid_first_sector_in_short_sat);
+ CDF_UNPACK(h->h_num_sectors_in_short_sat);
+ CDF_UNPACK(h->h_secid_first_sector_in_master_sat);
+ CDF_UNPACK(h->h_num_sectors_in_master_sat);
+ for (i = 0; i < __arraycount(h->h_master_sat); i++)
+ CDF_UNPACK(h->h_master_sat[i]);
+}
+
+void
+cdf_swap_dir(cdf_directory_t *d)
+{
+ d->d_namelen = CDF_TOLE2(d->d_namelen);
+ d->d_left_child = CDF_TOLE4(CAST(uint32_t, d->d_left_child));
+ d->d_right_child = CDF_TOLE4(CAST(uint32_t, d->d_right_child));
+ d->d_storage = CDF_TOLE4(CAST(uint32_t, d->d_storage));
+ d->d_storage_uuid[0] = CDF_TOLE8(d->d_storage_uuid[0]);
+ d->d_storage_uuid[1] = CDF_TOLE8(d->d_storage_uuid[1]);
+ d->d_flags = CDF_TOLE4(d->d_flags);
+ d->d_created = CDF_TOLE8(CAST(uint64_t, d->d_created));
+ d->d_modified = CDF_TOLE8(CAST(uint64_t, d->d_modified));
+ d->d_stream_first_sector = CDF_TOLE4(
+ CAST(uint32_t, d->d_stream_first_sector));
+ d->d_size = CDF_TOLE4(d->d_size);
+}
+
+void
+cdf_swap_class(cdf_classid_t *d)
+{
+ d->cl_dword = CDF_TOLE4(d->cl_dword);
+ d->cl_word[0] = CDF_TOLE2(d->cl_word[0]);
+ d->cl_word[1] = CDF_TOLE2(d->cl_word[1]);
+}
+
+void
+cdf_unpack_dir(cdf_directory_t *d, char *buf)
+{
+ size_t len = 0;
+
+ CDF_UNPACKA(d->d_name);
+ CDF_UNPACK(d->d_namelen);
+ CDF_UNPACK(d->d_type);
+ CDF_UNPACK(d->d_color);
+ CDF_UNPACK(d->d_left_child);
+ CDF_UNPACK(d->d_right_child);
+ CDF_UNPACK(d->d_storage);
+ CDF_UNPACKA(d->d_storage_uuid);
+ CDF_UNPACK(d->d_flags);
+ CDF_UNPACK(d->d_created);
+ CDF_UNPACK(d->d_modified);
+ CDF_UNPACK(d->d_stream_first_sector);
+ CDF_UNPACK(d->d_size);
+ CDF_UNPACK(d->d_unused0);
+}
+
+int
+cdf_zero_stream(cdf_stream_t *scn)
+{
+ scn->sst_len = 0;
+ scn->sst_dirlen = 0;
+ scn->sst_ss = 0;
+ free(scn->sst_tab);
+ scn->sst_tab = NULL;
+ return -1;
+}
+
+static size_t
+cdf_check_stream(const cdf_stream_t *sst, const cdf_header_t *h)
+{
+ size_t ss = sst->sst_dirlen < h->h_min_size_standard_stream ?
+ CDF_SHORT_SEC_SIZE(h) : CDF_SEC_SIZE(h);
+ assert(ss == sst->sst_ss);
+ return sst->sst_ss;
+}
+
+static int
+cdf_check_stream_offset(const cdf_stream_t *sst, const cdf_header_t *h,
+ const void *p, size_t tail, int line)
+{
+ const char *b = RCAST(const char *, sst->sst_tab);
+ const char *e = RCAST(const char *, p) + tail;
+ size_t ss = cdf_check_stream(sst, h);
+ /*LINTED*/(void)&line;
+ if (e >= b && CAST(size_t, e - b) <= ss * sst->sst_len)
+ return 0;
+ DPRINTF(("%d: offset begin %p < end %p || %" SIZE_T_FORMAT "u"
+ " > %" SIZE_T_FORMAT "u [%" SIZE_T_FORMAT "u %"
+ SIZE_T_FORMAT "u]\n", line, b, e, (size_t)(e - b),
+ ss * sst->sst_len, ss, sst->sst_len));
+ errno = EFTYPE;
+ return -1;
+}
+
+static ssize_t
+cdf_read(const cdf_info_t *info, off_t off, void *buf, size_t len)
+{
+ size_t siz = CAST(size_t, off + len);
+
+ if (CAST(off_t, off + len) != CAST(off_t, siz))
+ goto out;
+
+ if (info->i_buf != NULL && info->i_len >= siz) {
+ (void)memcpy(buf, &info->i_buf[off], len);
+ return CAST(ssize_t, len);
+ }
+
+ if (info->i_fd == -1)
+ goto out;
+
+ if (pread(info->i_fd, buf, len, off) != CAST(ssize_t, len))
+ return -1;
+
+ return CAST(ssize_t, len);
+out:
+ errno = EINVAL;
+ return -1;
+}
+
+int
+cdf_read_header(const cdf_info_t *info, cdf_header_t *h)
+{
+ char buf[512];
+
+ (void)memcpy(cdf_bo.s, "\01\02\03\04", 4);
+ if (cdf_read(info, CAST(off_t, 0), buf, sizeof(buf)) == -1)
+ return -1;
+ cdf_unpack_header(h, buf);
+ cdf_swap_header(h);
+ if (h->h_magic != CDF_MAGIC) {
+ DPRINTF(("Bad magic %#" INT64_T_FORMAT "x != %#"
+ INT64_T_FORMAT "x\n",
+ (unsigned long long)h->h_magic,
+ (unsigned long long)CDF_MAGIC));
+ goto out;
+ }
+ if (h->h_sec_size_p2 > 20) {
+ DPRINTF(("Bad sector size %hu\n", h->h_sec_size_p2));
+ goto out;
+ }
+ if (h->h_short_sec_size_p2 > 20) {
+ DPRINTF(("Bad short sector size %hu\n",
+ h->h_short_sec_size_p2));
+ goto out;
+ }
+ return 0;
+out:
+ errno = EFTYPE;
+ return -1;
+}
+
+
+ssize_t
+cdf_read_sector(const cdf_info_t *info, void *buf, size_t offs, size_t len,
+ const cdf_header_t *h, cdf_secid_t id)
+{
+ size_t ss = CDF_SEC_SIZE(h);
+ size_t pos;
+
+ if (SIZE_T_MAX / ss < CAST(size_t, id))
+ return -1;
+
+ pos = CDF_SEC_POS(h, id);
+ assert(ss == len);
+ return cdf_read(info, CAST(off_t, pos), RCAST(char *, buf) + offs, len);
+}
+
+ssize_t
+cdf_read_short_sector(const cdf_stream_t *sst, void *buf, size_t offs,
+ size_t len, const cdf_header_t *h, cdf_secid_t id)
+{
+ size_t ss = CDF_SHORT_SEC_SIZE(h);
+ size_t pos;
+
+ if (SIZE_T_MAX / ss < CAST(size_t, id))
+ return -1;
+
+ pos = CDF_SHORT_SEC_POS(h, id);
+ assert(ss == len);
+ if (pos + len > CDF_SEC_SIZE(h) * sst->sst_len) {
+ DPRINTF(("Out of bounds read %" SIZE_T_FORMAT "u > %"
+ SIZE_T_FORMAT "u\n",
+ pos + len, CDF_SEC_SIZE(h) * sst->sst_len));
+ goto out;
+ }
+ (void)memcpy(RCAST(char *, buf) + offs,
+ RCAST(const char *, sst->sst_tab) + pos, len);
+ return len;
+out:
+ errno = EFTYPE;
+ return -1;
+}
+
+/*
+ * Read the sector allocation table.
+ */
+int
+cdf_read_sat(const cdf_info_t *info, cdf_header_t *h, cdf_sat_t *sat)
+{
+ size_t i, j, k;
+ size_t ss = CDF_SEC_SIZE(h);
+ cdf_secid_t *msa, mid, sec;
+ size_t nsatpersec = (ss / sizeof(mid)) - 1;
+
+ for (i = 0; i < __arraycount(h->h_master_sat); i++)
+ if (h->h_master_sat[i] == CDF_SECID_FREE)
+ break;
+
+#define CDF_SEC_LIMIT (UINT32_MAX / (64 * ss))
+ if ((nsatpersec > 0 &&
+ h->h_num_sectors_in_master_sat > CDF_SEC_LIMIT / nsatpersec) ||
+ i > CDF_SEC_LIMIT) {
+ DPRINTF(("Number of sectors in master SAT too big %u %"
+ SIZE_T_FORMAT "u\n", h->h_num_sectors_in_master_sat, i));
+ errno = EFTYPE;
+ return -1;
+ }
+
+ sat->sat_len = h->h_num_sectors_in_master_sat * nsatpersec + i;
+ DPRINTF(("sat_len = %" SIZE_T_FORMAT "u ss = %" SIZE_T_FORMAT "u\n",
+ sat->sat_len, ss));
+ if ((sat->sat_tab = CAST(cdf_secid_t *, CDF_CALLOC(sat->sat_len, ss)))
+ == NULL)
+ return -1;
+
+ for (i = 0; i < __arraycount(h->h_master_sat); i++) {
+ if (h->h_master_sat[i] < 0)
+ break;
+ if (cdf_read_sector(info, sat->sat_tab, ss * i, ss, h,
+ h->h_master_sat[i]) != CAST(ssize_t, ss)) {
+ DPRINTF(("Reading sector %d", h->h_master_sat[i]));
+ goto out1;
+ }
+ }
+
+ if ((msa = CAST(cdf_secid_t *, CDF_CALLOC(1, ss))) == NULL)
+ goto out1;
+
+ mid = h->h_secid_first_sector_in_master_sat;
+ for (j = 0; j < h->h_num_sectors_in_master_sat; j++) {
+ if (mid < 0)
+ goto out;
+ if (j >= CDF_LOOP_LIMIT) {
+ DPRINTF(("Reading master sector loop limit"));
+ goto out3;
+ }
+ if (cdf_read_sector(info, msa, 0, ss, h, mid) !=
+ CAST(ssize_t, ss)) {
+ DPRINTF(("Reading master sector %d", mid));
+ goto out2;
+ }
+ for (k = 0; k < nsatpersec; k++, i++) {
+ sec = CDF_TOLE4(CAST(uint32_t, msa[k]));
+ if (sec < 0)
+ goto out;
+ if (i >= sat->sat_len) {
+ DPRINTF(("Out of bounds reading MSA %"
+ SIZE_T_FORMAT "u >= %" SIZE_T_FORMAT "u",
+ i, sat->sat_len));
+ goto out3;
+ }
+ if (cdf_read_sector(info, sat->sat_tab, ss * i, ss, h,
+ sec) != CAST(ssize_t, ss)) {
+ DPRINTF(("Reading sector %d",
+ CDF_TOLE4(msa[k])));
+ goto out2;
+ }
+ }
+ mid = CDF_TOLE4(CAST(uint32_t, msa[nsatpersec]));
+ }
+out:
+ sat->sat_len = i;
+ free(msa);
+ return 0;
+out3:
+ errno = EFTYPE;
+out2:
+ free(msa);
+out1:
+ free(sat->sat_tab);
+ return -1;
+}
+
+size_t
+cdf_count_chain(const cdf_sat_t *sat, cdf_secid_t sid, size_t size)
+{
+ size_t i, j;
+ cdf_secid_t maxsector = CAST(cdf_secid_t, (sat->sat_len * size)
+ / sizeof(maxsector));
+
+ DPRINTF(("Chain:"));
+ if (sid == CDF_SECID_END_OF_CHAIN) {
+ /* 0-length chain. */
+ DPRINTF((" empty\n"));
+ return 0;
+ }
+
+ for (j = i = 0; sid >= 0; i++, j++) {
+ DPRINTF((" %d", sid));
+ if (j >= CDF_LOOP_LIMIT) {
+ DPRINTF(("Counting chain loop limit"));
+ goto out;
+ }
+ if (sid >= maxsector) {
+ DPRINTF(("Sector %d >= %d\n", sid, maxsector));
+ goto out;
+ }
+ sid = CDF_TOLE4(CAST(uint32_t, sat->sat_tab[sid]));
+ }
+ if (i == 0) {
+ DPRINTF((" none, sid: %d\n", sid));
+ goto out;
+
+ }
+ DPRINTF(("\n"));
+ return i;
+out:
+ errno = EFTYPE;
+ return CAST(size_t, -1);
+}
+
+int
+cdf_read_long_sector_chain(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, cdf_secid_t sid, size_t len, cdf_stream_t *scn)
+{
+ size_t ss = CDF_SEC_SIZE(h), i, j;
+ ssize_t nr;
+ scn->sst_tab = NULL;
+ scn->sst_len = cdf_count_chain(sat, sid, ss);
+ scn->sst_dirlen = MAX(h->h_min_size_standard_stream, len);
+ scn->sst_ss = ss;
+
+ if (sid == CDF_SECID_END_OF_CHAIN || len == 0)
+ return cdf_zero_stream(scn);
+
+ if (scn->sst_len == CAST(size_t, -1))
+ goto out;
+
+ scn->sst_tab = CDF_CALLOC(scn->sst_len, ss);
+ if (scn->sst_tab == NULL)
+ return cdf_zero_stream(scn);
+
+ for (j = i = 0; sid >= 0; i++, j++) {
+ if (j >= CDF_LOOP_LIMIT) {
+ DPRINTF(("Read long sector chain loop limit"));
+ goto out;
+ }
+ if (i >= scn->sst_len) {
+ DPRINTF(("Out of bounds reading long sector chain "
+ "%" SIZE_T_FORMAT "u > %" SIZE_T_FORMAT "u\n", i,
+ scn->sst_len));
+ goto out;
+ }
+ if ((nr = cdf_read_sector(info, scn->sst_tab, i * ss, ss, h,
+ sid)) != CAST(ssize_t, ss)) {
+ if (i == scn->sst_len - 1 && nr > 0) {
+ /* Last sector might be truncated */
+ return 0;
+ }
+ DPRINTF(("Reading long sector chain %d", sid));
+ goto out;
+ }
+ sid = CDF_TOLE4(CAST(uint32_t, sat->sat_tab[sid]));
+ }
+ return 0;
+out:
+ errno = EFTYPE;
+ return cdf_zero_stream(scn);
+}
+
+int
+cdf_read_short_sector_chain(const cdf_header_t *h,
+ const cdf_sat_t *ssat, const cdf_stream_t *sst,
+ cdf_secid_t sid, size_t len, cdf_stream_t *scn)
+{
+ size_t ss = CDF_SHORT_SEC_SIZE(h), i, j;
+ scn->sst_tab = NULL;
+ scn->sst_len = cdf_count_chain(ssat, sid, CDF_SEC_SIZE(h));
+ scn->sst_dirlen = len;
+ scn->sst_ss = ss;
+
+ if (scn->sst_len == CAST(size_t, -1))
+ goto out;
+
+ scn->sst_tab = CDF_CALLOC(scn->sst_len, ss);
+ if (scn->sst_tab == NULL)
+ return cdf_zero_stream(scn);
+
+ for (j = i = 0; sid >= 0; i++, j++) {
+ if (j >= CDF_LOOP_LIMIT) {
+ DPRINTF(("Read short sector chain loop limit"));
+ goto out;
+ }
+ if (i >= scn->sst_len) {
+ DPRINTF(("Out of bounds reading short sector chain "
+ "%" SIZE_T_FORMAT "u > %" SIZE_T_FORMAT "u\n",
+ i, scn->sst_len));
+ goto out;
+ }
+ if (cdf_read_short_sector(sst, scn->sst_tab, i * ss, ss, h,
+ sid) != CAST(ssize_t, ss)) {
+ DPRINTF(("Reading short sector chain %d", sid));
+ goto out;
+ }
+ sid = CDF_TOLE4(CAST(uint32_t, ssat->sat_tab[sid]));
+ }
+ return 0;
+out:
+ errno = EFTYPE;
+ return cdf_zero_stream(scn);
+}
+
+int
+cdf_read_sector_chain(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, const cdf_sat_t *ssat, const cdf_stream_t *sst,
+ cdf_secid_t sid, size_t len, cdf_stream_t *scn)
+{
+
+ if (len < h->h_min_size_standard_stream && sst->sst_tab != NULL)
+ return cdf_read_short_sector_chain(h, ssat, sst, sid, len,
+ scn);
+ else
+ return cdf_read_long_sector_chain(info, h, sat, sid, len, scn);
+}
+
+int
+cdf_read_dir(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, cdf_dir_t *dir)
+{
+ size_t i, j;
+ size_t ss = CDF_SEC_SIZE(h), ns, nd;
+ char *buf;
+ cdf_secid_t sid = h->h_secid_first_directory;
+
+ ns = cdf_count_chain(sat, sid, ss);
+ if (ns == CAST(size_t, -1))
+ return -1;
+
+ nd = ss / CDF_DIRECTORY_SIZE;
+
+ dir->dir_len = ns * nd;
+ dir->dir_tab = CAST(cdf_directory_t *,
+ CDF_CALLOC(dir->dir_len, sizeof(dir->dir_tab[0])));
+ if (dir->dir_tab == NULL)
+ return -1;
+
+ if ((buf = CAST(char *, CDF_MALLOC(ss))) == NULL) {
+ free(dir->dir_tab);
+ return -1;
+ }
+
+ for (j = i = 0; i < ns; i++, j++) {
+ if (j >= CDF_LOOP_LIMIT) {
+ DPRINTF(("Read dir loop limit"));
+ goto out;
+ }
+ if (cdf_read_sector(info, buf, 0, ss, h, sid) !=
+ CAST(ssize_t, ss)) {
+ DPRINTF(("Reading directory sector %d", sid));
+ goto out;
+ }
+ for (j = 0; j < nd; j++) {
+ cdf_unpack_dir(&dir->dir_tab[i * nd + j],
+ &buf[j * CDF_DIRECTORY_SIZE]);
+ }
+ sid = CDF_TOLE4(CAST(uint32_t, sat->sat_tab[sid]));
+ }
+ if (NEED_SWAP)
+ for (i = 0; i < dir->dir_len; i++)
+ cdf_swap_dir(&dir->dir_tab[i]);
+ free(buf);
+ return 0;
+out:
+ free(dir->dir_tab);
+ free(buf);
+ errno = EFTYPE;
+ return -1;
+}
+
+
+int
+cdf_read_ssat(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, cdf_sat_t *ssat)
+{
+ size_t i, j;
+ size_t ss = CDF_SEC_SIZE(h);
+ cdf_secid_t sid = h->h_secid_first_sector_in_short_sat;
+
+ ssat->sat_tab = NULL;
+ ssat->sat_len = cdf_count_chain(sat, sid, ss);
+ if (ssat->sat_len == CAST(size_t, -1))
+ goto out;
+
+ ssat->sat_tab = CAST(cdf_secid_t *, CDF_CALLOC(ssat->sat_len, ss));
+ if (ssat->sat_tab == NULL)
+ goto out1;
+
+ for (j = i = 0; sid >= 0; i++, j++) {
+ if (j >= CDF_LOOP_LIMIT) {
+ DPRINTF(("Read short sat sector loop limit"));
+ goto out;
+ }
+ if (i >= ssat->sat_len) {
+ DPRINTF(("Out of bounds reading short sector chain "
+ "%" SIZE_T_FORMAT "u > %" SIZE_T_FORMAT "u\n", i,
+ ssat->sat_len));
+ goto out;
+ }
+ if (cdf_read_sector(info, ssat->sat_tab, i * ss, ss, h, sid) !=
+ CAST(ssize_t, ss)) {
+ DPRINTF(("Reading short sat sector %d", sid));
+ goto out1;
+ }
+ sid = CDF_TOLE4(CAST(uint32_t, sat->sat_tab[sid]));
+ }
+ return 0;
+out:
+ errno = EFTYPE;
+out1:
+ free(ssat->sat_tab);
+ return -1;
+}
+
+int
+cdf_read_short_stream(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, const cdf_dir_t *dir, cdf_stream_t *scn,
+ const cdf_directory_t **root)
+{
+ size_t i;
+ const cdf_directory_t *d;
+
+ *root = NULL;
+ for (i = 0; i < dir->dir_len; i++)
+ if (dir->dir_tab[i].d_type == CDF_DIR_TYPE_ROOT_STORAGE)
+ break;
+
+ /* If the it is not there, just fake it; some docs don't have it */
+ if (i == dir->dir_len) {
+ DPRINTF(("Cannot find root storage dir\n"));
+ goto out;
+ }
+ d = &dir->dir_tab[i];
+ *root = d;
+
+ /* If the it is not there, just fake it; some docs don't have it */
+ if (d->d_stream_first_sector < 0) {
+ DPRINTF(("No first secror in dir\n"));
+ goto out;
+ }
+
+ return cdf_read_long_sector_chain(info, h, sat,
+ d->d_stream_first_sector, d->d_size, scn);
+out:
+ scn->sst_tab = NULL;
+ (void)cdf_zero_stream(scn);
+ return 0;
+}
+
+static int
+cdf_namecmp(const char *d, const uint16_t *s, size_t l)
+{
+ for (; l--; d++, s++)
+ if (*d != CDF_TOLE2(*s))
+ return CAST(unsigned char, *d) - CDF_TOLE2(*s);
+ return 0;
+}
+
+int
+cdf_read_doc_summary_info(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, const cdf_sat_t *ssat, const cdf_stream_t *sst,
+ const cdf_dir_t *dir, cdf_stream_t *scn)
+{
+ return cdf_read_user_stream(info, h, sat, ssat, sst, dir,
+ "\05DocumentSummaryInformation", scn);
+}
+
+int
+cdf_read_summary_info(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, const cdf_sat_t *ssat, const cdf_stream_t *sst,
+ const cdf_dir_t *dir, cdf_stream_t *scn)
+{
+ return cdf_read_user_stream(info, h, sat, ssat, sst, dir,
+ "\05SummaryInformation", scn);
+}
+
+int
+cdf_read_user_stream(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, const cdf_sat_t *ssat, const cdf_stream_t *sst,
+ const cdf_dir_t *dir, const char *name, cdf_stream_t *scn)
+{
+ const cdf_directory_t *d;
+ int i = cdf_find_stream(dir, name, CDF_DIR_TYPE_USER_STREAM);
+
+ if (i <= 0) {
+ memset(scn, 0, sizeof(*scn));
+ return -1;
+ }
+
+ d = &dir->dir_tab[i - 1];
+ return cdf_read_sector_chain(info, h, sat, ssat, sst,
+ d->d_stream_first_sector, d->d_size, scn);
+}
+
+int
+cdf_find_stream(const cdf_dir_t *dir, const char *name, int type)
+{
+ size_t i, name_len = strlen(name) + 1;
+
+ for (i = dir->dir_len; i > 0; i--)
+ if (dir->dir_tab[i - 1].d_type == type &&
+ cdf_namecmp(name, dir->dir_tab[i - 1].d_name, name_len)
+ == 0)
+ break;
+ if (i > 0)
+ return CAST(int, i);
+
+ DPRINTF(("Cannot find type %d `%s'\n", type, name));
+ errno = ESRCH;
+ return 0;
+}
+
+#define CDF_SHLEN_LIMIT (UINT32_MAX / 64)
+#define CDF_PROP_LIMIT (UINT32_MAX / (64 * sizeof(cdf_property_info_t)))
+
+static const void *
+cdf_offset(const void *p, size_t l)
+{
+ return CAST(const void *, CAST(const uint8_t *, p) + l);
+}
+
+static const uint8_t *
+cdf_get_property_info_pos(const cdf_stream_t *sst, const cdf_header_t *h,
+ const uint8_t *p, const uint8_t *e, size_t i)
+{
+ size_t tail = (i << 1) + 1;
+ size_t ofs;
+
+ if (p >= e) {
+ DPRINTF(("Past end %p < %p\n", e, p));
+ return NULL;
+ }
+
+ if (cdf_check_stream_offset(sst, h, p, (tail + 1) * sizeof(uint32_t),
+ __LINE__) == -1)
+ return NULL;
+
+ ofs = CDF_GETUINT32(p, tail);
+ if (ofs < 2 * sizeof(uint32_t)) {
+ DPRINTF(("Offset too small %zu\n", ofs));
+ return NULL;
+ }
+
+ ofs -= 2 * sizeof(uint32_t);
+ if (ofs > CAST(size_t, e - p)) {
+ DPRINTF(("Offset too big %zu %td\n", ofs, e - p));
+ return NULL;
+ }
+
+ return CAST(const uint8_t *, cdf_offset(CAST(const void *, p), ofs));
+}
+
+static cdf_property_info_t *
+cdf_grow_info(cdf_property_info_t **info, size_t *maxcount, size_t incr)
+{
+ cdf_property_info_t *inp;
+ size_t newcount = *maxcount + incr;
+
+ if (newcount > CDF_PROP_LIMIT) {
+ DPRINTF(("exceeded property limit %" SIZE_T_FORMAT "u > %"
+ SIZE_T_FORMAT "u\n", newcount, CDF_PROP_LIMIT));
+ goto out;
+ }
+ inp = CAST(cdf_property_info_t *,
+ CDF_REALLOC(*info, newcount * sizeof(*inp)));
+ if (inp == NULL)
+ goto out;
+
+ *info = inp;
+ *maxcount = newcount;
+ return inp;
+out:
+ free(*info);
+ *maxcount = 0;
+ *info = NULL;
+ return NULL;
+}
+
+static int
+cdf_copy_info(cdf_property_info_t *inp, const void *p, const void *e,
+ size_t len)
+{
+ if (inp->pi_type & CDF_VECTOR)
+ return 0;
+
+ if (CAST(size_t, CAST(const char *, e) - CAST(const char *, p)) < len)
+ return 0;
+
+ (void)memcpy(&inp->pi_val, p, len);
+
+ switch (len) {
+ case 2:
+ inp->pi_u16 = CDF_TOLE2(inp->pi_u16);
+ break;
+ case 4:
+ inp->pi_u32 = CDF_TOLE4(inp->pi_u32);
+ break;
+ case 8:
+ inp->pi_u64 = CDF_TOLE8(inp->pi_u64);
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+int
+cdf_read_property_info(const cdf_stream_t *sst, const cdf_header_t *h,
+ uint32_t offs, cdf_property_info_t **info, size_t *count, size_t *maxcount)
+{
+ const cdf_section_header_t *shp;
+ cdf_section_header_t sh;
+ const uint8_t *p, *q, *e;
+ size_t i, o4, nelements, j, slen, left;
+ cdf_property_info_t *inp;
+
+ if (offs > UINT32_MAX / 4) {
+ errno = EFTYPE;
+ goto out;
+ }
+ shp = CAST(const cdf_section_header_t *,
+ cdf_offset(sst->sst_tab, offs));
+ if (cdf_check_stream_offset(sst, h, shp, sizeof(*shp), __LINE__) == -1)
+ goto out;
+ sh.sh_len = CDF_TOLE4(shp->sh_len);
+ if (sh.sh_len > CDF_SHLEN_LIMIT) {
+ errno = EFTYPE;
+ goto out;
+ }
+
+ if (cdf_check_stream_offset(sst, h, shp, sh.sh_len, __LINE__) == -1)
+ goto out;
+
+ sh.sh_properties = CDF_TOLE4(shp->sh_properties);
+ DPRINTF(("section len: %u properties %u\n", sh.sh_len,
+ sh.sh_properties));
+ if (sh.sh_properties > CDF_PROP_LIMIT)
+ goto out;
+ inp = cdf_grow_info(info, maxcount, sh.sh_properties);
+ if (inp == NULL)
+ goto out;
+ inp += *count;
+ *count += sh.sh_properties;
+ p = CAST(const uint8_t *, cdf_offset(sst->sst_tab, offs + sizeof(sh)));
+ e = CAST(const uint8_t *, cdf_offset(shp, sh.sh_len));
+ if (p >= e || cdf_check_stream_offset(sst, h, e, 0, __LINE__) == -1)
+ goto out;
+
+ for (i = 0; i < sh.sh_properties; i++) {
+ if ((q = cdf_get_property_info_pos(sst, h, p, e, i)) == NULL)
+ goto out;
+ inp[i].pi_id = CDF_GETUINT32(p, i << 1);
+ left = CAST(size_t, e - q);
+ if (left < sizeof(uint32_t)) {
+ DPRINTF(("short info (no type)_\n"));
+ goto out;
+ }
+ inp[i].pi_type = CDF_GETUINT32(q, 0);
+ DPRINTF(("%" SIZE_T_FORMAT "u) id=%#x type=%#x offs=%#tx,%#x\n",
+ i, inp[i].pi_id, inp[i].pi_type, q - p, offs));
+ if (inp[i].pi_type & CDF_VECTOR) {
+ if (left < sizeof(uint32_t) * 2) {
+ DPRINTF(("missing CDF_VECTOR length\n"));
+ goto out;
+ }
+ nelements = CDF_GETUINT32(q, 1);
+ if (nelements > CDF_ELEMENT_LIMIT || nelements == 0) {
+ DPRINTF(("CDF_VECTOR with nelements == %"
+ SIZE_T_FORMAT "u\n", nelements));
+ goto out;
+ }
+ slen = 2;
+ } else {
+ nelements = 1;
+ slen = 1;
+ }
+ o4 = slen * sizeof(uint32_t);
+ if (inp[i].pi_type & (CDF_ARRAY|CDF_BYREF|CDF_RESERVED))
+ goto unknown;
+ switch (inp[i].pi_type & CDF_TYPEMASK) {
+ case CDF_NULL:
+ case CDF_EMPTY:
+ break;
+ case CDF_SIGNED16:
+ if (!cdf_copy_info(&inp[i], &q[o4], e, sizeof(int16_t)))
+ goto unknown;
+ break;
+ case CDF_SIGNED32:
+ case CDF_BOOL:
+ case CDF_UNSIGNED32:
+ case CDF_FLOAT:
+ if (!cdf_copy_info(&inp[i], &q[o4], e, sizeof(int32_t)))
+ goto unknown;
+ break;
+ case CDF_SIGNED64:
+ case CDF_UNSIGNED64:
+ case CDF_DOUBLE:
+ case CDF_FILETIME:
+ if (!cdf_copy_info(&inp[i], &q[o4], e, sizeof(int64_t)))
+ goto unknown;
+ break;
+ case CDF_LENGTH32_STRING:
+ case CDF_LENGTH32_WSTRING:
+ if (nelements > 1) {
+ size_t nelem = inp - *info;
+ inp = cdf_grow_info(info, maxcount, nelements);
+ if (inp == NULL)
+ goto out;
+ inp += nelem;
+ }
+ for (j = 0; j < nelements && i < sh.sh_properties;
+ j++, i++)
+ {
+ uint32_t l;
+
+ if (o4 + sizeof(uint32_t) > left)
+ goto out;
+
+ l = CDF_GETUINT32(q, slen);
+ o4 += sizeof(uint32_t);
+ if (o4 + l > left)
+ goto out;
+
+ inp[i].pi_str.s_len = l;
+ inp[i].pi_str.s_buf = CAST(const char *,
+ CAST(const void *, &q[o4]));
+
+ DPRINTF(("o=%" SIZE_T_FORMAT "u l=%d(%"
+ SIZE_T_FORMAT "u), t=%" SIZE_T_FORMAT
+ "u s=%.*s\n", o4, l,
+ CDF_ROUND(l, sizeof(l)),
+ left, (int)l, inp[i].pi_str.s_buf));
+
+ if (l & 1)
+ l++;
+
+ slen += l >> 1;
+ o4 = slen * sizeof(uint32_t);
+ }
+ i--;
+ break;
+ case CDF_CLIPBOARD:
+ if (inp[i].pi_type & CDF_VECTOR)
+ goto unknown;
+ break;
+ default:
+ unknown:
+ memset(&inp[i].pi_val, 0, sizeof(inp[i].pi_val));
+ DPRINTF(("Don't know how to deal with %#x\n",
+ inp[i].pi_type));
+ break;
+ }
+ }
+ return 0;
+out:
+ free(*info);
+ *info = NULL;
+ *count = 0;
+ *maxcount = 0;
+ errno = EFTYPE;
+ return -1;
+}
+
+int
+cdf_unpack_summary_info(const cdf_stream_t *sst, const cdf_header_t *h,
+ cdf_summary_info_header_t *ssi, cdf_property_info_t **info, size_t *count)
+{
+ size_t maxcount;
+ const cdf_summary_info_header_t *si =
+ CAST(const cdf_summary_info_header_t *, sst->sst_tab);
+ const cdf_section_declaration_t *sd =
+ CAST(const cdf_section_declaration_t *, RCAST(const void *,
+ RCAST(const char *, sst->sst_tab)
+ + CDF_SECTION_DECLARATION_OFFSET));
+
+ if (cdf_check_stream_offset(sst, h, si, sizeof(*si), __LINE__) == -1 ||
+ cdf_check_stream_offset(sst, h, sd, sizeof(*sd), __LINE__) == -1)
+ return -1;
+ ssi->si_byte_order = CDF_TOLE2(si->si_byte_order);
+ ssi->si_os_version = CDF_TOLE2(si->si_os_version);
+ ssi->si_os = CDF_TOLE2(si->si_os);
+ ssi->si_class = si->si_class;
+ cdf_swap_class(&ssi->si_class);
+ ssi->si_count = CDF_TOLE4(si->si_count);
+ *count = 0;
+ maxcount = 0;
+ *info = NULL;
+ if (cdf_read_property_info(sst, h, CDF_TOLE4(sd->sd_offset), info,
+ count, &maxcount) == -1)
+ return -1;
+ return 0;
+}
+
+
+#define extract_catalog_field(t, f, l) \
+ if (b + l + sizeof(cep->f) > eb) { \
+ cep->ce_namlen = 0; \
+ break; \
+ } \
+ memcpy(&cep->f, b + (l), sizeof(cep->f)); \
+ ce[i].f = CAST(t, CDF_TOLE(cep->f))
+
+int
+cdf_unpack_catalog(const cdf_header_t *h, const cdf_stream_t *sst,
+ cdf_catalog_t **cat)
+{
+ size_t ss = cdf_check_stream(sst, h);
+ const char *b = CAST(const char *, sst->sst_tab);
+ const char *nb, *eb = b + ss * sst->sst_len;
+ size_t nr, i, j, k;
+ cdf_catalog_entry_t *ce;
+ uint16_t reclen;
+ const uint16_t *np;
+
+ for (nr = 0;; nr++) {
+ memcpy(&reclen, b, sizeof(reclen));
+ reclen = CDF_TOLE2(reclen);
+ if (reclen == 0)
+ break;
+ b += reclen;
+ if (b > eb)
+ break;
+ }
+ if (nr == 0)
+ return -1;
+ nr--;
+ *cat = CAST(cdf_catalog_t *,
+ CDF_MALLOC(sizeof(cdf_catalog_t) + nr * sizeof(*ce)));
+ if (*cat == NULL)
+ return -1;
+ ce = (*cat)->cat_e;
+ memset(ce, 0, nr * sizeof(*ce));
+ b = CAST(const char *, sst->sst_tab);
+ for (j = i = 0; i < nr; b += reclen) {
+ cdf_catalog_entry_t *cep = &ce[j];
+ uint16_t rlen;
+
+ extract_catalog_field(uint16_t, ce_namlen, 0);
+ extract_catalog_field(uint16_t, ce_num, 4);
+ extract_catalog_field(uint64_t, ce_timestamp, 8);
+ reclen = cep->ce_namlen;
+
+ if (reclen < 14) {
+ cep->ce_namlen = 0;
+ continue;
+ }
+
+ cep->ce_namlen = __arraycount(cep->ce_name) - 1;
+ rlen = reclen - 14;
+ if (cep->ce_namlen > rlen)
+ cep->ce_namlen = rlen;
+
+ np = CAST(const uint16_t *, CAST(const void *, (b + 16)));
+ nb = CAST(const char *, CAST(const void *,
+ (np + cep->ce_namlen)));
+ if (nb > eb) {
+ cep->ce_namlen = 0;
+ break;
+ }
+
+ for (k = 0; k < cep->ce_namlen; k++)
+ cep->ce_name[k] = np[k]; /* XXX: CDF_TOLE2? */
+ cep->ce_name[cep->ce_namlen] = 0;
+ j = i;
+ i++;
+ }
+ (*cat)->cat_num = j;
+ return 0;
+}
+
+int
+cdf_print_classid(char *buf, size_t buflen, const cdf_classid_t *id)
+{
+ return snprintf(buf, buflen, "%.8x-%.4x-%.4x-%.2x%.2x-"
+ "%.2x%.2x%.2x%.2x%.2x%.2x", id->cl_dword, id->cl_word[0],
+ id->cl_word[1], id->cl_two[0], id->cl_two[1], id->cl_six[0],
+ id->cl_six[1], id->cl_six[2], id->cl_six[3], id->cl_six[4],
+ id->cl_six[5]);
+}
+
+static const struct {
+ uint32_t v;
+ const char *n;
+} vn[] = {
+ { CDF_PROPERTY_CODE_PAGE, "Code page" },
+ { CDF_PROPERTY_TITLE, "Title" },
+ { CDF_PROPERTY_SUBJECT, "Subject" },
+ { CDF_PROPERTY_AUTHOR, "Author" },
+ { CDF_PROPERTY_KEYWORDS, "Keywords" },
+ { CDF_PROPERTY_COMMENTS, "Comments" },
+ { CDF_PROPERTY_TEMPLATE, "Template" },
+ { CDF_PROPERTY_LAST_SAVED_BY, "Last Saved By" },
+ { CDF_PROPERTY_REVISION_NUMBER, "Revision Number" },
+ { CDF_PROPERTY_TOTAL_EDITING_TIME, "Total Editing Time" },
+ { CDF_PROPERTY_LAST_PRINTED, "Last Printed" },
+ { CDF_PROPERTY_CREATE_TIME, "Create Time/Date" },
+ { CDF_PROPERTY_LAST_SAVED_TIME, "Last Saved Time/Date" },
+ { CDF_PROPERTY_NUMBER_OF_PAGES, "Number of Pages" },
+ { CDF_PROPERTY_NUMBER_OF_WORDS, "Number of Words" },
+ { CDF_PROPERTY_NUMBER_OF_CHARACTERS, "Number of Characters" },
+ { CDF_PROPERTY_THUMBNAIL, "Thumbnail" },
+ { CDF_PROPERTY_NAME_OF_APPLICATION, "Name of Creating Application" },
+ { CDF_PROPERTY_SECURITY, "Security" },
+ { CDF_PROPERTY_LOCALE_ID, "Locale ID" },
+};
+
+int
+cdf_print_property_name(char *buf, size_t bufsiz, uint32_t p)
+{
+ size_t i;
+
+ for (i = 0; i < __arraycount(vn); i++)
+ if (vn[i].v == p)
+ return snprintf(buf, bufsiz, "%s", vn[i].n);
+ return snprintf(buf, bufsiz, "%#x", p);
+}
+
+int
+cdf_print_elapsed_time(char *buf, size_t bufsiz, cdf_timestamp_t ts)
+{
+ int len = 0;
+ int days, hours, mins, secs;
+
+ ts /= CDF_TIME_PREC;
+ secs = CAST(int, ts % 60);
+ ts /= 60;
+ mins = CAST(int, ts % 60);
+ ts /= 60;
+ hours = CAST(int, ts % 24);
+ ts /= 24;
+ days = CAST(int, ts);
+
+ if (days) {
+ len += snprintf(buf + len, bufsiz - len, "%dd+", days);
+ if (CAST(size_t, len) >= bufsiz)
+ return len;
+ }
+
+ if (days || hours) {
+ len += snprintf(buf + len, bufsiz - len, "%.2d:", hours);
+ if (CAST(size_t, len) >= bufsiz)
+ return len;
+ }
+
+ len += snprintf(buf + len, bufsiz - len, "%.2d:", mins);
+ if (CAST(size_t, len) >= bufsiz)
+ return len;
+
+ len += snprintf(buf + len, bufsiz - len, "%.2d", secs);
+ return len;
+}
+
+char *
+cdf_u16tos8(char *buf, size_t len, const uint16_t *p)
+{
+ size_t i;
+ for (i = 0; i < len && p[i]; i++)
+ buf[i] = CAST(char, p[i]);
+ buf[i] = '\0';
+ return buf;
+}
+
+#ifdef CDF_DEBUG
+void
+cdf_dump_header(const cdf_header_t *h)
+{
+ size_t i;
+
+#define DUMP(a, b) (void)fprintf(stderr, "%40.40s = " a "\n", # b, h->h_ ## b)
+#define DUMP2(a, b) (void)fprintf(stderr, "%40.40s = " a " (" a ")\n", # b, \
+ h->h_ ## b, 1 << h->h_ ## b)
+ DUMP("%d", revision);
+ DUMP("%d", version);
+ DUMP("%#x", byte_order);
+ DUMP2("%d", sec_size_p2);
+ DUMP2("%d", short_sec_size_p2);
+ DUMP("%d", num_sectors_in_sat);
+ DUMP("%d", secid_first_directory);
+ DUMP("%d", min_size_standard_stream);
+ DUMP("%d", secid_first_sector_in_short_sat);
+ DUMP("%d", num_sectors_in_short_sat);
+ DUMP("%d", secid_first_sector_in_master_sat);
+ DUMP("%d", num_sectors_in_master_sat);
+ for (i = 0; i < __arraycount(h->h_master_sat); i++) {
+ if (h->h_master_sat[i] == CDF_SECID_FREE)
+ break;
+ (void)fprintf(stderr, "%35.35s[%.3" SIZE_T_FORMAT "u] = %d\n",
+ "master_sat", i, h->h_master_sat[i]);
+ }
+}
+
+void
+cdf_dump_sat(const char *prefix, const cdf_sat_t *sat, size_t size)
+{
+ size_t i, j, s = size / sizeof(cdf_secid_t);
+
+ for (i = 0; i < sat->sat_len; i++) {
+ (void)fprintf(stderr, "%s[%" SIZE_T_FORMAT "u]:\n%.6"
+ SIZE_T_FORMAT "u: ", prefix, i, i * s);
+ for (j = 0; j < s; j++) {
+ (void)fprintf(stderr, "%5d, ",
+ CDF_TOLE4(sat->sat_tab[s * i + j]));
+ if ((j + 1) % 10 == 0)
+ (void)fprintf(stderr, "\n%.6" SIZE_T_FORMAT
+ "u: ", i * s + j + 1);
+ }
+ (void)fprintf(stderr, "\n");
+ }
+}
+
+void
+cdf_dump(const void *v, size_t len)
+{
+ size_t i, j;
+ const unsigned char *p = v;
+ char abuf[16];
+
+ (void)fprintf(stderr, "%.4x: ", 0);
+ for (i = 0, j = 0; i < len; i++, p++) {
+ (void)fprintf(stderr, "%.2x ", *p);
+ abuf[j++] = isprint(*p) ? *p : '.';
+ if (j == 16) {
+ j = 0;
+ abuf[15] = '\0';
+ (void)fprintf(stderr, "%s\n%.4" SIZE_T_FORMAT "x: ",
+ abuf, i + 1);
+ }
+ }
+ (void)fprintf(stderr, "\n");
+}
+
+void
+cdf_dump_stream(const cdf_stream_t *sst)
+{
+ size_t ss = sst->sst_ss;
+ cdf_dump(sst->sst_tab, ss * sst->sst_len);
+}
+
+void
+cdf_dump_dir(const cdf_info_t *info, const cdf_header_t *h,
+ const cdf_sat_t *sat, const cdf_sat_t *ssat, const cdf_stream_t *sst,
+ const cdf_dir_t *dir)
+{
+ size_t i, j;
+ cdf_directory_t *d;
+ char name[__arraycount(d->d_name)];
+ cdf_stream_t scn;
+ struct timespec ts;
+
+ static const char *types[] = { "empty", "user storage",
+ "user stream", "lockbytes", "property", "root storage" };
+
+ for (i = 0; i < dir->dir_len; i++) {
+ char buf[26];
+ d = &dir->dir_tab[i];
+ for (j = 0; j < sizeof(name); j++)
+ name[j] = (char)CDF_TOLE2(d->d_name[j]);
+ (void)fprintf(stderr, "Directory %" SIZE_T_FORMAT "u: %s\n",
+ i, name);
+ if (d->d_type < __arraycount(types))
+ (void)fprintf(stderr, "Type: %s\n", types[d->d_type]);
+ else
+ (void)fprintf(stderr, "Type: %d\n", d->d_type);
+ (void)fprintf(stderr, "Color: %s\n",
+ d->d_color ? "black" : "red");
+ (void)fprintf(stderr, "Left child: %d\n", d->d_left_child);
+ (void)fprintf(stderr, "Right child: %d\n", d->d_right_child);
+ (void)fprintf(stderr, "Flags: %#x\n", d->d_flags);
+ cdf_timestamp_to_timespec(&ts, d->d_created);
+ (void)fprintf(stderr, "Created %s", cdf_ctime(&ts.tv_sec, buf));
+ cdf_timestamp_to_timespec(&ts, d->d_modified);
+ (void)fprintf(stderr, "Modified %s",
+ cdf_ctime(&ts.tv_sec, buf));
+ (void)fprintf(stderr, "Stream %d\n", d->d_stream_first_sector);
+ (void)fprintf(stderr, "Size %d\n", d->d_size);
+ switch (d->d_type) {
+ case CDF_DIR_TYPE_USER_STORAGE:
+ (void)fprintf(stderr, "Storage: %d\n", d->d_storage);
+ break;
+ case CDF_DIR_TYPE_USER_STREAM:
+ if (sst == NULL)
+ break;
+ if (cdf_read_sector_chain(info, h, sat, ssat, sst,
+ d->d_stream_first_sector, d->d_size, &scn) == -1) {
+ warn("Can't read stream for %s at %d len %d",
+ name, d->d_stream_first_sector, d->d_size);
+ break;
+ }
+ cdf_dump_stream(&scn);
+ free(scn.sst_tab);
+ break;
+ default:
+ break;
+ }
+
+ }
+}
+
+void
+cdf_dump_property_info(const cdf_property_info_t *info, size_t count)
+{
+ cdf_timestamp_t tp;
+ struct timespec ts;
+ char buf[64];
+ size_t i, j;
+
+ for (i = 0; i < count; i++) {
+ cdf_print_property_name(buf, sizeof(buf), info[i].pi_id);
+ (void)fprintf(stderr, "%" SIZE_T_FORMAT "u) %s: ", i, buf);
+ switch (info[i].pi_type) {
+ case CDF_NULL:
+ break;
+ case CDF_SIGNED16:
+ (void)fprintf(stderr, "signed 16 [%hd]\n",
+ info[i].pi_s16);
+ break;
+ case CDF_SIGNED32:
+ (void)fprintf(stderr, "signed 32 [%d]\n",
+ info[i].pi_s32);
+ break;
+ case CDF_UNSIGNED32:
+ (void)fprintf(stderr, "unsigned 32 [%u]\n",
+ info[i].pi_u32);
+ break;
+ case CDF_FLOAT:
+ (void)fprintf(stderr, "float [%g]\n",
+ info[i].pi_f);
+ break;
+ case CDF_DOUBLE:
+ (void)fprintf(stderr, "double [%g]\n",
+ info[i].pi_d);
+ break;
+ case CDF_LENGTH32_STRING:
+ (void)fprintf(stderr, "string %u [%.*s]\n",
+ info[i].pi_str.s_len,
+ info[i].pi_str.s_len, info[i].pi_str.s_buf);
+ break;
+ case CDF_LENGTH32_WSTRING:
+ (void)fprintf(stderr, "string %u [",
+ info[i].pi_str.s_len);
+ for (j = 0; j < info[i].pi_str.s_len - 1; j++)
+ (void)fputc(info[i].pi_str.s_buf[j << 1], stderr);
+ (void)fprintf(stderr, "]\n");
+ break;
+ case CDF_FILETIME:
+ tp = info[i].pi_tp;
+ if (tp < 1000000000000000LL) {
+ cdf_print_elapsed_time(buf, sizeof(buf), tp);
+ (void)fprintf(stderr, "timestamp %s\n", buf);
+ } else {
+ char tbuf[26];
+ cdf_timestamp_to_timespec(&ts, tp);
+ (void)fprintf(stderr, "timestamp %s",
+ cdf_ctime(&ts.tv_sec, tbuf));
+ }
+ break;
+ case CDF_CLIPBOARD:
+ (void)fprintf(stderr, "CLIPBOARD %u\n", info[i].pi_u32);
+ break;
+ default:
+ DPRINTF(("Don't know how to deal with %#x\n",
+ info[i].pi_type));
+ break;
+ }
+ }
+}
+
+
+void
+cdf_dump_summary_info(const cdf_header_t *h, const cdf_stream_t *sst)
+{
+ char buf[128];
+ cdf_summary_info_header_t ssi;
+ cdf_property_info_t *info;
+ size_t count;
+
+ (void)&h;
+ if (cdf_unpack_summary_info(sst, h, &ssi, &info, &count) == -1)
+ return;
+ (void)fprintf(stderr, "Endian: %#x\n", ssi.si_byte_order);
+ (void)fprintf(stderr, "Os Version %d.%d\n", ssi.si_os_version & 0xff,
+ ssi.si_os_version >> 8);
+ (void)fprintf(stderr, "Os %d\n", ssi.si_os);
+ cdf_print_classid(buf, sizeof(buf), &ssi.si_class);
+ (void)fprintf(stderr, "Class %s\n", buf);
+ (void)fprintf(stderr, "Count %d\n", ssi.si_count);
+ cdf_dump_property_info(info, count);
+ free(info);
+}
+
+
+void
+cdf_dump_catalog(const cdf_header_t *h, const cdf_stream_t *sst)
+{
+ cdf_catalog_t *cat;
+ cdf_unpack_catalog(h, sst, &cat);
+ const cdf_catalog_entry_t *ce = cat->cat_e;
+ struct timespec ts;
+ char tbuf[64], sbuf[256];
+ size_t i;
+
+ printf("Catalog:\n");
+ for (i = 0; i < cat->cat_num; i++) {
+ cdf_timestamp_to_timespec(&ts, ce[i].ce_timestamp);
+ printf("\t%d %s %s", ce[i].ce_num,
+ cdf_u16tos8(sbuf, ce[i].ce_namlen, ce[i].ce_name),
+ cdf_ctime(&ts.tv_sec, tbuf));
+ }
+ free(cat);
+}
+
+#endif
+
+#ifdef TEST
+int
+main(int argc, char *argv[])
+{
+ int i;
+ cdf_header_t h;
+ cdf_sat_t sat, ssat;
+ cdf_stream_t sst, scn;
+ cdf_dir_t dir;
+ cdf_info_t info;
+ const cdf_directory_t *root;
+#ifdef __linux__
+#define getprogname() __progname
+ extern char *__progname;
+#endif
+ if (argc < 2) {
+ (void)fprintf(stderr, "Usage: %s <filename>\n", getprogname());
+ return -1;
+ }
+
+ info.i_buf = NULL;
+ info.i_len = 0;
+ for (i = 1; i < argc; i++) {
+ if ((info.i_fd = open(argv[1], O_RDONLY)) == -1)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[1]);
+
+ if (cdf_read_header(&info, &h) == -1)
+ err(EXIT_FAILURE, "Cannot read header");
+#ifdef CDF_DEBUG
+ cdf_dump_header(&h);
+#endif
+
+ if (cdf_read_sat(&info, &h, &sat) == -1)
+ err(EXIT_FAILURE, "Cannot read sat");
+#ifdef CDF_DEBUG
+ cdf_dump_sat("SAT", &sat, CDF_SEC_SIZE(&h));
+#endif
+
+ if (cdf_read_ssat(&info, &h, &sat, &ssat) == -1)
+ err(EXIT_FAILURE, "Cannot read ssat");
+#ifdef CDF_DEBUG
+ cdf_dump_sat("SSAT", &ssat, CDF_SHORT_SEC_SIZE(&h));
+#endif
+
+ if (cdf_read_dir(&info, &h, &sat, &dir) == -1)
+ err(EXIT_FAILURE, "Cannot read dir");
+
+ if (cdf_read_short_stream(&info, &h, &sat, &dir, &sst, &root)
+ == -1)
+ err(EXIT_FAILURE, "Cannot read short stream");
+#ifdef CDF_DEBUG
+ cdf_dump_stream(&sst);
+#endif
+
+#ifdef CDF_DEBUG
+ cdf_dump_dir(&info, &h, &sat, &ssat, &sst, &dir);
+#endif
+
+
+ if (cdf_read_summary_info(&info, &h, &sat, &ssat, &sst, &dir,
+ &scn) == -1)
+ warn("Cannot read summary info");
+#ifdef CDF_DEBUG
+ else
+ cdf_dump_summary_info(&h, &scn);
+#endif
+ if (cdf_read_user_stream(&info, &h, &sat, &ssat, &sst,
+ &dir, "Catalog", &scn) == -1)
+ warn("Cannot read catalog");
+#ifdef CDF_DEBUG
+ else
+ cdf_dump_catalog(&h, &scn);
+#endif
+
+ (void)close(info.i_fd);
+ }
+
+ return 0;
+}
+#endif
diff --git a/src/cdf.h b/src/cdf.h
new file mode 100644
index 0000000..0505666
--- /dev/null
+++ b/src/cdf.h
@@ -0,0 +1,353 @@
+/*-
+ * Copyright (c) 2008 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+/*
+ * Parse Composite Document Files, the format used in Microsoft Office
+ * document files before they switched to zipped XML.
+ * Info from: http://sc.openoffice.org/compdocfileformat.pdf
+ *
+ * N.B. This is the "Composite Document File" format, and not the
+ * "Compound Document Format", nor the "Channel Definition Format".
+ */
+
+#ifndef _H_CDF_
+#define _H_CDF_
+
+#ifdef WIN32
+#include <winsock2.h>
+#define timespec timeval
+#define tv_nsec tv_usec
+#endif
+#ifdef __DJGPP__
+#define timespec timeval
+#define tv_nsec tv_usec
+#endif
+
+typedef int32_t cdf_secid_t;
+
+#define CDF_LOOP_LIMIT 10000
+#define CDF_ELEMENT_LIMIT 100000
+
+#define CDF_SECID_NULL 0
+#define CDF_SECID_FREE -1
+#define CDF_SECID_END_OF_CHAIN -2
+#define CDF_SECID_SECTOR_ALLOCATION_TABLE -3
+#define CDF_SECID_MASTER_SECTOR_ALLOCATION_TABLE -4
+
+typedef struct {
+ uint64_t h_magic;
+#define CDF_MAGIC 0xE11AB1A1E011CFD0LL
+ uint64_t h_uuid[2];
+ uint16_t h_revision;
+ uint16_t h_version;
+ uint16_t h_byte_order;
+ uint16_t h_sec_size_p2;
+ uint16_t h_short_sec_size_p2;
+ uint8_t h_unused0[10];
+ uint32_t h_num_sectors_in_sat;
+ uint32_t h_secid_first_directory;
+ uint8_t h_unused1[4];
+ uint32_t h_min_size_standard_stream;
+ cdf_secid_t h_secid_first_sector_in_short_sat;
+ uint32_t h_num_sectors_in_short_sat;
+ cdf_secid_t h_secid_first_sector_in_master_sat;
+ uint32_t h_num_sectors_in_master_sat;
+ cdf_secid_t h_master_sat[436/4];
+} cdf_header_t;
+
+#define CDF_SEC_SIZE(h) CAST(size_t, 1 << (h)->h_sec_size_p2)
+#define CDF_SEC_POS(h, secid) (CDF_SEC_SIZE(h) + (secid) * CDF_SEC_SIZE(h))
+#define CDF_SHORT_SEC_SIZE(h) CAST(size_t, 1 << (h)->h_short_sec_size_p2)
+#define CDF_SHORT_SEC_POS(h, secid) ((secid) * CDF_SHORT_SEC_SIZE(h))
+
+typedef int32_t cdf_dirid_t;
+#define CDF_DIRID_NULL -1
+
+typedef int64_t cdf_timestamp_t;
+#define CDF_BASE_YEAR 1601
+#define CDF_TIME_PREC 10000000
+
+typedef struct {
+ uint16_t d_name[32];
+ uint16_t d_namelen;
+ uint8_t d_type;
+#define CDF_DIR_TYPE_EMPTY 0
+#define CDF_DIR_TYPE_USER_STORAGE 1
+#define CDF_DIR_TYPE_USER_STREAM 2
+#define CDF_DIR_TYPE_LOCKBYTES 3
+#define CDF_DIR_TYPE_PROPERTY 4
+#define CDF_DIR_TYPE_ROOT_STORAGE 5
+ uint8_t d_color;
+#define CDF_DIR_COLOR_READ 0
+#define CDF_DIR_COLOR_BLACK 1
+ cdf_dirid_t d_left_child;
+ cdf_dirid_t d_right_child;
+ cdf_dirid_t d_storage;
+ uint64_t d_storage_uuid[2];
+ uint32_t d_flags;
+ cdf_timestamp_t d_created;
+ cdf_timestamp_t d_modified;
+ cdf_secid_t d_stream_first_sector;
+ uint32_t d_size;
+ uint32_t d_unused0;
+} cdf_directory_t;
+
+#define CDF_DIRECTORY_SIZE 128
+
+typedef struct {
+ cdf_secid_t *sat_tab;
+ size_t sat_len;
+} cdf_sat_t;
+
+typedef struct {
+ cdf_directory_t *dir_tab;
+ size_t dir_len;
+} cdf_dir_t;
+
+typedef struct {
+ void *sst_tab;
+ size_t sst_len; /* Number of sectors */
+ size_t sst_dirlen; /* Directory sector size */
+ size_t sst_ss; /* Sector size */
+} cdf_stream_t;
+
+typedef struct {
+ uint32_t cl_dword;
+ uint16_t cl_word[2];
+ uint8_t cl_two[2];
+ uint8_t cl_six[6];
+} cdf_classid_t;
+
+typedef struct {
+ uint16_t si_byte_order;
+ uint16_t si_zero;
+ uint16_t si_os_version;
+ uint16_t si_os;
+ cdf_classid_t si_class;
+ uint32_t si_count;
+} cdf_summary_info_header_t;
+
+#define CDF_SECTION_DECLARATION_OFFSET 0x1c
+
+typedef struct {
+ cdf_classid_t sd_class;
+ uint32_t sd_offset;
+} cdf_section_declaration_t;
+
+typedef struct {
+ uint32_t sh_len;
+ uint32_t sh_properties;
+} cdf_section_header_t;
+
+typedef struct {
+ uint32_t pi_id;
+ uint32_t pi_type;
+ union {
+ uint16_t _pi_u16;
+ int16_t _pi_s16;
+ uint32_t _pi_u32;
+ int32_t _pi_s32;
+ uint64_t _pi_u64;
+ int64_t _pi_s64;
+ cdf_timestamp_t _pi_tp;
+ float _pi_f;
+ double _pi_d;
+ struct {
+ uint32_t s_len;
+ const char *s_buf;
+ } _pi_str;
+ } pi_val;
+#define pi_u64 pi_val._pi_u64
+#define pi_s64 pi_val._pi_s64
+#define pi_u32 pi_val._pi_u32
+#define pi_s32 pi_val._pi_s32
+#define pi_u16 pi_val._pi_u16
+#define pi_s16 pi_val._pi_s16
+#define pi_f pi_val._pi_f
+#define pi_d pi_val._pi_d
+#define pi_tp pi_val._pi_tp
+#define pi_str pi_val._pi_str
+} cdf_property_info_t;
+
+#define CDF_ROUND(val, by) (((val) + (by) - 1) & ~((by) - 1))
+
+/* Variant type definitions */
+#define CDF_EMPTY 0x00000000
+#define CDF_NULL 0x00000001
+#define CDF_SIGNED16 0x00000002
+#define CDF_SIGNED32 0x00000003
+#define CDF_FLOAT 0x00000004
+#define CDF_DOUBLE 0x00000005
+#define CDF_CY 0x00000006
+#define CDF_DATE 0x00000007
+#define CDF_BSTR 0x00000008
+#define CDF_DISPATCH 0x00000009
+#define CDF_ERROR 0x0000000a
+#define CDF_BOOL 0x0000000b
+#define CDF_VARIANT 0x0000000c
+#define CDF_UNKNOWN 0x0000000d
+#define CDF_DECIMAL 0x0000000e
+#define CDF_SIGNED8 0x00000010
+#define CDF_UNSIGNED8 0x00000011
+#define CDF_UNSIGNED16 0x00000012
+#define CDF_UNSIGNED32 0x00000013
+#define CDF_SIGNED64 0x00000014
+#define CDF_UNSIGNED64 0x00000015
+#define CDF_INT 0x00000016
+#define CDF_UINT 0x00000017
+#define CDF_VOID 0x00000018
+#define CDF_HRESULT 0x00000019
+#define CDF_PTR 0x0000001a
+#define CDF_SAFEARRAY 0x0000001b
+#define CDF_CARRAY 0x0000001c
+#define CDF_USERDEFINED 0x0000001d
+#define CDF_LENGTH32_STRING 0x0000001e
+#define CDF_LENGTH32_WSTRING 0x0000001f
+#define CDF_FILETIME 0x00000040
+#define CDF_BLOB 0x00000041
+#define CDF_STREAM 0x00000042
+#define CDF_STORAGE 0x00000043
+#define CDF_STREAMED_OBJECT 0x00000044
+#define CDF_STORED_OBJECT 0x00000045
+#define CDF_BLOB_OBJECT 0x00000046
+#define CDF_CLIPBOARD 0x00000047
+#define CDF_CLSID 0x00000048
+#define CDF_VECTOR 0x00001000
+#define CDF_ARRAY 0x00002000
+#define CDF_BYREF 0x00004000
+#define CDF_RESERVED 0x00008000
+#define CDF_ILLEGAL 0x0000ffff
+#define CDF_ILLEGALMASKED 0x00000fff
+#define CDF_TYPEMASK 0x00000fff
+
+#define CDF_PROPERTY_CODE_PAGE 0x00000001
+#define CDF_PROPERTY_TITLE 0x00000002
+#define CDF_PROPERTY_SUBJECT 0x00000003
+#define CDF_PROPERTY_AUTHOR 0x00000004
+#define CDF_PROPERTY_KEYWORDS 0x00000005
+#define CDF_PROPERTY_COMMENTS 0x00000006
+#define CDF_PROPERTY_TEMPLATE 0x00000007
+#define CDF_PROPERTY_LAST_SAVED_BY 0x00000008
+#define CDF_PROPERTY_REVISION_NUMBER 0x00000009
+#define CDF_PROPERTY_TOTAL_EDITING_TIME 0x0000000a
+#define CDF_PROPERTY_LAST_PRINTED 0X0000000b
+#define CDF_PROPERTY_CREATE_TIME 0x0000000c
+#define CDF_PROPERTY_LAST_SAVED_TIME 0x0000000d
+#define CDF_PROPERTY_NUMBER_OF_PAGES 0x0000000e
+#define CDF_PROPERTY_NUMBER_OF_WORDS 0x0000000f
+#define CDF_PROPERTY_NUMBER_OF_CHARACTERS 0x00000010
+#define CDF_PROPERTY_THUMBNAIL 0x00000011
+#define CDF_PROPERTY_NAME_OF_APPLICATION 0x00000012
+#define CDF_PROPERTY_SECURITY 0x00000013
+#define CDF_PROPERTY_LOCALE_ID 0x80000000
+
+typedef struct {
+ int i_fd;
+ const unsigned char *i_buf;
+ size_t i_len;
+} cdf_info_t;
+
+
+typedef struct {
+ uint16_t ce_namlen;
+ uint32_t ce_num;
+ uint64_t ce_timestamp;
+ uint16_t ce_name[256];
+} cdf_catalog_entry_t;
+
+typedef struct {
+ size_t cat_num;
+ cdf_catalog_entry_t cat_e[1];
+} cdf_catalog_t;
+
+struct timespec;
+int cdf_timestamp_to_timespec(struct timespec *, cdf_timestamp_t);
+int cdf_timespec_to_timestamp(cdf_timestamp_t *, const struct timespec *);
+int cdf_read_header(const cdf_info_t *, cdf_header_t *);
+void cdf_swap_header(cdf_header_t *);
+void cdf_unpack_header(cdf_header_t *, char *);
+void cdf_swap_dir(cdf_directory_t *);
+void cdf_unpack_dir(cdf_directory_t *, char *);
+void cdf_swap_class(cdf_classid_t *);
+ssize_t cdf_read_sector(const cdf_info_t *, void *, size_t, size_t,
+ const cdf_header_t *, cdf_secid_t);
+ssize_t cdf_read_short_sector(const cdf_stream_t *, void *, size_t, size_t,
+ const cdf_header_t *, cdf_secid_t);
+int cdf_read_sat(const cdf_info_t *, cdf_header_t *, cdf_sat_t *);
+size_t cdf_count_chain(const cdf_sat_t *, cdf_secid_t, size_t);
+int cdf_read_long_sector_chain(const cdf_info_t *, const cdf_header_t *,
+ const cdf_sat_t *, cdf_secid_t, size_t, cdf_stream_t *);
+int cdf_read_short_sector_chain(const cdf_header_t *, const cdf_sat_t *,
+ const cdf_stream_t *, cdf_secid_t, size_t, cdf_stream_t *);
+int cdf_read_sector_chain(const cdf_info_t *, const cdf_header_t *,
+ const cdf_sat_t *, const cdf_sat_t *, const cdf_stream_t *, cdf_secid_t,
+ size_t, cdf_stream_t *);
+int cdf_read_dir(const cdf_info_t *, const cdf_header_t *, const cdf_sat_t *,
+ cdf_dir_t *);
+int cdf_read_ssat(const cdf_info_t *, const cdf_header_t *, const cdf_sat_t *,
+ cdf_sat_t *);
+int cdf_read_short_stream(const cdf_info_t *, const cdf_header_t *,
+ const cdf_sat_t *, const cdf_dir_t *, cdf_stream_t *,
+ const cdf_directory_t **);
+int cdf_read_property_info(const cdf_stream_t *, const cdf_header_t *, uint32_t,
+ cdf_property_info_t **, size_t *, size_t *);
+int cdf_read_user_stream(const cdf_info_t *, const cdf_header_t *,
+ const cdf_sat_t *, const cdf_sat_t *, const cdf_stream_t *,
+ const cdf_dir_t *, const char *, cdf_stream_t *);
+int cdf_find_stream(const cdf_dir_t *, const char *, int);
+int cdf_zero_stream(cdf_stream_t *);
+int cdf_read_doc_summary_info(const cdf_info_t *, const cdf_header_t *,
+ const cdf_sat_t *, const cdf_sat_t *, const cdf_stream_t *,
+ const cdf_dir_t *, cdf_stream_t *);
+int cdf_read_summary_info(const cdf_info_t *, const cdf_header_t *,
+ const cdf_sat_t *, const cdf_sat_t *, const cdf_stream_t *,
+ const cdf_dir_t *, cdf_stream_t *);
+int cdf_unpack_summary_info(const cdf_stream_t *, const cdf_header_t *,
+ cdf_summary_info_header_t *, cdf_property_info_t **, size_t *);
+int cdf_unpack_catalog(const cdf_header_t *, const cdf_stream_t *,
+ cdf_catalog_t **);
+int cdf_print_classid(char *, size_t, const cdf_classid_t *);
+int cdf_print_property_name(char *, size_t, uint32_t);
+int cdf_print_elapsed_time(char *, size_t, cdf_timestamp_t);
+uint16_t cdf_tole2(uint16_t);
+uint32_t cdf_tole4(uint32_t);
+uint64_t cdf_tole8(uint64_t);
+char *cdf_ctime(const time_t *, char *);
+char *cdf_u16tos8(char *, size_t, const uint16_t *);
+
+#ifdef CDF_DEBUG
+void cdf_dump_header(const cdf_header_t *);
+void cdf_dump_sat(const char *, const cdf_sat_t *, size_t);
+void cdf_dump(const void *, size_t);
+void cdf_dump_stream(const cdf_stream_t *);
+void cdf_dump_dir(const cdf_info_t *, const cdf_header_t *, const cdf_sat_t *,
+ const cdf_sat_t *, const cdf_stream_t *, const cdf_dir_t *);
+void cdf_dump_property_info(const cdf_property_info_t *, size_t);
+void cdf_dump_summary_info(const cdf_header_t *, const cdf_stream_t *);
+void cdf_dump_catalog(const cdf_header_t *, const cdf_stream_t *);
+#endif
+
+
+#endif /* _H_CDF_ */
diff --git a/src/cdf.mk b/src/cdf.mk
new file mode 100644
index 0000000..681616a
--- /dev/null
+++ b/src/cdf.mk
@@ -0,0 +1,3 @@
+CFLAGS+=-DTEST -DCDF_DEBUG -g -DHAVE_CONFIG_H -I..
+cdf: cdf.o cdf_time.o
+ ${CC} ${CFLAGS} -o $@ $>
diff --git a/src/cdf_time.c b/src/cdf_time.c
new file mode 100644
index 0000000..63a68fe
--- /dev/null
+++ b/src/cdf_time.c
@@ -0,0 +1,203 @@
+/*-
+ * Copyright (c) 2008 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: cdf_time.c,v 1.23 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+#include <time.h>
+#ifdef TEST
+#include <err.h>
+#endif
+#include <string.h>
+
+#include "cdf.h"
+
+#define isleap(y) ((((y) % 4) == 0) && \
+ ((((y) % 100) != 0) || (((y) % 400) == 0)))
+
+static const int mdays[] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ * Return the number of days between jan 01 1601 and jan 01 of year.
+ */
+static int
+cdf_getdays(int year)
+{
+ int days = 0;
+ int y;
+
+ for (y = CDF_BASE_YEAR; y < year; y++)
+ days += isleap(y) + 365;
+
+ return days;
+}
+
+/*
+ * Return the day within the month
+ */
+static int
+cdf_getday(int year, int days)
+{
+ size_t m;
+
+ for (m = 0; m < __arraycount(mdays); m++) {
+ int sub = mdays[m] + (m == 1 && isleap(year));
+ if (days < sub)
+ return days;
+ days -= sub;
+ }
+ return days;
+}
+
+/*
+ * Return the 0...11 month number.
+ */
+static int
+cdf_getmonth(int year, int days)
+{
+ size_t m;
+
+ for (m = 0; m < __arraycount(mdays); m++) {
+ days -= mdays[m];
+ if (m == 1 && isleap(year))
+ days--;
+ if (days <= 0)
+ return CAST(int, m);
+ }
+ return CAST(int, m);
+}
+
+int
+cdf_timestamp_to_timespec(struct timespec *ts, cdf_timestamp_t t)
+{
+ struct tm tm;
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ static char UTC[] = "UTC";
+#endif
+ int rdays;
+
+ /* Unit is 100's of nanoseconds */
+ ts->tv_nsec = (t % CDF_TIME_PREC) * 100;
+
+ t /= CDF_TIME_PREC;
+ tm.tm_sec = CAST(int, t % 60);
+ t /= 60;
+
+ tm.tm_min = CAST(int, t % 60);
+ t /= 60;
+
+ tm.tm_hour = CAST(int, t % 24);
+ t /= 24;
+
+ /* XXX: Approx */
+ tm.tm_year = CAST(int, CDF_BASE_YEAR + (t / 365));
+
+ rdays = cdf_getdays(tm.tm_year);
+ t -= rdays - 1;
+ tm.tm_mday = cdf_getday(tm.tm_year, CAST(int, t));
+ tm.tm_mon = cdf_getmonth(tm.tm_year, CAST(int, t));
+ tm.tm_wday = 0;
+ tm.tm_yday = 0;
+ tm.tm_isdst = 0;
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ tm.tm_gmtoff = 0;
+#endif
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm.tm_zone = UTC;
+#endif
+ tm.tm_year -= 1900;
+ ts->tv_sec = mktime(&tm);
+ if (ts->tv_sec == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+int
+/*ARGSUSED*/
+cdf_timespec_to_timestamp(cdf_timestamp_t *t, const struct timespec *ts)
+{
+#ifndef __lint__
+ (void)&t;
+ (void)&ts;
+#endif
+#ifdef notyet
+ struct tm tm;
+ if (gmtime_r(&ts->ts_sec, &tm) == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ *t = (ts->ts_nsec / 100) * CDF_TIME_PREC;
+ *t += tm.tm_sec;
+ *t += tm.tm_min * 60;
+ *t += tm.tm_hour * 60 * 60;
+ *t += tm.tm_mday * 60 * 60 * 24;
+#endif
+ return 0;
+}
+
+char *
+cdf_ctime(const time_t *sec, char *buf)
+{
+ char *ptr = ctime_r(sec, buf);
+ if (ptr != NULL)
+ return buf;
+#ifdef WIN32
+ (void)snprintf(buf, 26, "*Bad* 0x%16.16I64x\n",
+ CAST(long long, *sec));
+#else
+ (void)snprintf(buf, 26, "*Bad* %#16.16" INT64_T_FORMAT "x\n",
+ CAST(long long, *sec));
+#endif
+ return buf;
+}
+
+
+#ifdef TEST_TIME
+int
+main(int argc, char *argv[])
+{
+ struct timespec ts;
+ char buf[25];
+ static const cdf_timestamp_t tst = 0x01A5E403C2D59C00ULL;
+ static const char *ref = "Sat Apr 23 01:30:00 1977";
+ char *p, *q;
+
+ cdf_timestamp_to_timespec(&ts, tst);
+ p = cdf_ctime(&ts.tv_sec, buf);
+ if ((q = strchr(p, '\n')) != NULL)
+ *q = '\0';
+ if (strcmp(ref, p) != 0)
+ errx(1, "Error date %s != %s\n", ref, p);
+ return 0;
+}
+#endif
diff --git a/src/compress.c b/src/compress.c
new file mode 100644
index 0000000..282f2a3
--- /dev/null
+++ b/src/compress.c
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * compress routines:
+ * zmagic() - returns 0 if not recognized, uncompresses and prints
+ * information if recognized
+ * uncompress(method, old, n, newch) - uncompress old into new,
+ * using method, return sizeof new
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: compress.c,v 1.154 2022/12/26 17:35:45 christos Exp $")
+#endif
+
+#include "magic.h"
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SPAWN_H
+#include <spawn.h>
+#endif
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <signal.h>
+#ifndef HAVE_SIG_T
+typedef void (*sig_t)(int);
+#endif /* HAVE_SIG_T */
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_SYS_TIME_H)
+#include <sys/time.h>
+#endif
+
+#if defined(HAVE_ZLIB_H) && defined(ZLIBSUPPORT)
+#define BUILTIN_DECOMPRESS
+#include <zlib.h>
+#endif
+
+#if defined(HAVE_BZLIB_H) && defined(BZLIBSUPPORT)
+#define BUILTIN_BZLIB
+#include <bzlib.h>
+#endif
+
+#if defined(HAVE_LZMA_H) && defined(XZLIBSUPPORT)
+#define BUILTIN_XZLIB
+#include <lzma.h>
+#endif
+
+#if defined(HAVE_ZSTD_H) && defined(ZSTDLIBSUPPORT)
+#define BUILTIN_ZSTDLIB
+#include <zstd.h>
+#include <zstd_errors.h>
+#endif
+
+#if defined(HAVE_LZLIB_H) && defined(LZLIBSUPPORT)
+#define BUILTIN_LZLIB
+#include <lzlib.h>
+#endif
+
+#ifdef DEBUG
+int tty = -1;
+#define DPRINTF(...) do { \
+ if (tty == -1) \
+ tty = open("/dev/tty", O_RDWR); \
+ if (tty == -1) \
+ abort(); \
+ dprintf(tty, __VA_ARGS__); \
+} while (/*CONSTCOND*/0)
+#else
+#define DPRINTF(...)
+#endif
+
+#ifdef ZLIBSUPPORT
+/*
+ * The following python code is not really used because ZLIBSUPPORT is only
+ * defined if we have a built-in zlib, and the built-in zlib handles that.
+ * That is not true for android where we have zlib.h and not -lz.
+ */
+static const char zlibcode[] =
+ "import sys, zlib; sys.stdout.write(zlib.decompress(sys.stdin.read()))";
+
+static const char *zlib_args[] = { "python", "-c", zlibcode, NULL };
+
+static int
+zlibcmp(const unsigned char *buf)
+{
+ unsigned short x = 1;
+ unsigned char *s = CAST(unsigned char *, CAST(void *, &x));
+
+ if ((buf[0] & 0xf) != 8 || (buf[0] & 0x80) != 0)
+ return 0;
+ if (s[0] != 1) /* endianness test */
+ x = buf[0] | (buf[1] << 8);
+ else
+ x = buf[1] | (buf[0] << 8);
+ if (x % 31)
+ return 0;
+ return 1;
+}
+#endif
+
+static int
+lzmacmp(const unsigned char *buf)
+{
+ if (buf[0] != 0x5d || buf[1] || buf[2])
+ return 0;
+ if (buf[12] && buf[12] != 0xff)
+ return 0;
+ return 1;
+}
+
+#define gzip_flags "-cd"
+#define lrzip_flags "-do"
+#define lzip_flags gzip_flags
+
+static const char *gzip_args[] = {
+ "gzip", gzip_flags, NULL
+};
+static const char *uncompress_args[] = {
+ "uncompress", "-c", NULL
+};
+static const char *bzip2_args[] = {
+ "bzip2", "-cd", NULL
+};
+static const char *lzip_args[] = {
+ "lzip", lzip_flags, NULL
+};
+static const char *xz_args[] = {
+ "xz", "-cd", NULL
+};
+static const char *lrzip_args[] = {
+ "lrzip", lrzip_flags, NULL
+};
+static const char *lz4_args[] = {
+ "lz4", "-cd", NULL
+};
+static const char *zstd_args[] = {
+ "zstd", "-cd", NULL
+};
+
+#define do_zlib NULL
+#define do_bzlib NULL
+
+file_private const struct {
+ union {
+ const char *magic;
+ int (*func)(const unsigned char *);
+ } u;
+ int maglen;
+ const char **argv;
+ void *unused;
+} compr[] = {
+#define METH_FROZEN 2
+#define METH_BZIP 7
+#define METH_XZ 9
+#define METH_LZIP 8
+#define METH_ZSTD 12
+#define METH_LZMA 13
+#define METH_ZLIB 14
+ { { .magic = "\037\235" }, 2, gzip_args, NULL }, /* 0, compressed */
+ /* Uncompress can get stuck; so use gzip first if we have it
+ * Idea from Damien Clark, thanks! */
+ { { .magic = "\037\235" }, 2, uncompress_args, NULL },/* 1, compressed */
+ { { .magic = "\037\213" }, 2, gzip_args, do_zlib },/* 2, gzipped */
+ { { .magic = "\037\236" }, 2, gzip_args, NULL }, /* 3, frozen */
+ { { .magic = "\037\240" }, 2, gzip_args, NULL }, /* 4, SCO LZH */
+ /* the standard pack utilities do not accept standard input */
+ { { .magic = "\037\036" }, 2, gzip_args, NULL }, /* 5, packed */
+ { { .magic = "PK\3\4" }, 4, gzip_args, NULL }, /* 6, pkziped */
+ /* ...only first file examined */
+ { { .magic = "BZh" }, 3, bzip2_args, do_bzlib },/* 7, bzip2-ed */
+ { { .magic = "LZIP" }, 4, lzip_args, NULL }, /* 8, lzip-ed */
+ { { .magic = "\3757zXZ\0" },6, xz_args, NULL }, /* 9, XZ Util */
+ { { .magic = "LRZI" }, 4, lrzip_args, NULL }, /* 10, LRZIP */
+ { { .magic = "\004\"M\030" },4, lz4_args, NULL }, /* 11, LZ4 */
+ { { .magic = "\x28\xB5\x2F\xFD" }, 4, zstd_args, NULL },/* 12, zstd */
+ { { .func = lzmacmp }, -13, xz_args, NULL }, /* 13, lzma */
+#ifdef ZLIBSUPPORT
+ { { .func = zlibcmp }, -2, zlib_args, NULL }, /* 14, zlib */
+#endif
+};
+
+#define OKDATA 0
+#define NODATA 1
+#define ERRDATA 2
+
+file_private ssize_t swrite(int, const void *, size_t);
+#if HAVE_FORK
+file_private size_t ncompr = __arraycount(compr);
+file_private int uncompressbuf(int, size_t, size_t, int, const unsigned char *,
+ unsigned char **, size_t *);
+#ifdef BUILTIN_DECOMPRESS
+file_private int uncompresszlib(const unsigned char *, unsigned char **, size_t,
+ size_t *, int);
+file_private int uncompressgzipped(const unsigned char *, unsigned char **, size_t,
+ size_t *, int);
+#endif
+#ifdef BUILTIN_BZLIB
+file_private int uncompressbzlib(const unsigned char *, unsigned char **, size_t,
+ size_t *, int);
+#endif
+#ifdef BUILTIN_XZLIB
+file_private int uncompressxzlib(const unsigned char *, unsigned char **, size_t,
+ size_t *, int);
+#endif
+#ifdef BUILTIN_ZSTDLIB
+file_private int uncompresszstd(const unsigned char *, unsigned char **, size_t,
+ size_t *, int);
+#endif
+#ifdef BUILTIN_LZLIB
+file_private int uncompresslzlib(const unsigned char *, unsigned char **, size_t,
+ size_t *, int);
+#endif
+
+static int makeerror(unsigned char **, size_t *, const char *, ...)
+ __attribute__((__format__(__printf__, 3, 4)));
+file_private const char *methodname(size_t);
+
+file_private int
+format_decompression_error(struct magic_set *ms, size_t i, unsigned char *buf)
+{
+ unsigned char *p;
+ int mime = ms->flags & MAGIC_MIME;
+
+ if (!mime)
+ return file_printf(ms, "ERROR:[%s: %s]", methodname(i), buf);
+
+ for (p = buf; *p; p++)
+ if (!isalnum(*p))
+ *p = '-';
+
+ return file_printf(ms, "application/x-decompression-error-%s-%s",
+ methodname(i), buf);
+}
+
+file_protected int
+file_zmagic(struct magic_set *ms, const struct buffer *b, const char *name)
+{
+ unsigned char *newbuf = NULL;
+ size_t i, nsz;
+ char *rbuf;
+ file_pushbuf_t *pb;
+ int urv, prv, rv = 0;
+ int mime = ms->flags & MAGIC_MIME;
+ int fd = b->fd;
+ const unsigned char *buf = CAST(const unsigned char *, b->fbuf);
+ size_t nbytes = b->flen;
+ int sa_saved = 0;
+ struct sigaction sig_act;
+
+ if ((ms->flags & MAGIC_COMPRESS) == 0)
+ return 0;
+
+ for (i = 0; i < ncompr; i++) {
+ int zm;
+ if (nbytes < CAST(size_t, abs(compr[i].maglen)))
+ continue;
+ if (compr[i].maglen < 0) {
+ zm = (*compr[i].u.func)(buf);
+ } else {
+ zm = memcmp(buf, compr[i].u.magic,
+ CAST(size_t, compr[i].maglen)) == 0;
+ }
+
+ if (!zm)
+ continue;
+
+ /* Prevent SIGPIPE death if child dies unexpectedly */
+ if (!sa_saved) {
+ //We can use sig_act for both new and old, but
+ struct sigaction new_act;
+ memset(&new_act, 0, sizeof(new_act));
+ new_act.sa_handler = SIG_IGN;
+ sa_saved = sigaction(SIGPIPE, &new_act, &sig_act) != -1;
+ }
+
+ nsz = nbytes;
+ free(newbuf);
+ urv = uncompressbuf(fd, ms->bytes_max, i,
+ (ms->flags & MAGIC_NO_COMPRESS_FORK), buf, &newbuf, &nsz);
+ DPRINTF("uncompressbuf = %d, %s, %" SIZE_T_FORMAT "u\n", urv,
+ (char *)newbuf, nsz);
+ switch (urv) {
+ case OKDATA:
+ case ERRDATA:
+ ms->flags &= ~MAGIC_COMPRESS;
+ if (urv == ERRDATA)
+ prv = format_decompression_error(ms, i, newbuf);
+ else
+ prv = file_buffer(ms, -1, NULL, name, newbuf,
+ nsz);
+ if (prv == -1)
+ goto error;
+ rv = 1;
+ if ((ms->flags & MAGIC_COMPRESS_TRANSP) != 0)
+ goto out;
+ if (mime != MAGIC_MIME && mime != 0)
+ goto out;
+ if ((file_printf(ms,
+ mime ? " compressed-encoding=" : " (")) == -1)
+ goto error;
+ if ((pb = file_push_buffer(ms)) == NULL)
+ goto error;
+ /*
+ * XXX: If file_buffer fails here, we overwrite
+ * the compressed text. FIXME.
+ */
+ if (file_buffer(ms, -1, NULL, NULL, buf, nbytes) == -1)
+ {
+ if (file_pop_buffer(ms, pb) != NULL)
+ abort();
+ goto error;
+ }
+ if ((rbuf = file_pop_buffer(ms, pb)) != NULL) {
+ if (file_printf(ms, "%s", rbuf) == -1) {
+ free(rbuf);
+ goto error;
+ }
+ free(rbuf);
+ }
+ if (!mime && file_printf(ms, ")") == -1)
+ goto error;
+ /*FALLTHROUGH*/
+ case NODATA:
+ break;
+ default:
+ abort();
+ /*NOTREACHED*/
+ error:
+ rv = -1;
+ break;
+ }
+ }
+out:
+ DPRINTF("rv = %d\n", rv);
+
+ if (sa_saved && sig_act.sa_handler != SIG_IGN)
+ (void)sigaction(SIGPIPE, &sig_act, NULL);
+
+ free(newbuf);
+ ms->flags |= MAGIC_COMPRESS;
+ DPRINTF("Zmagic returns %d\n", rv);
+ return rv;
+}
+#endif
+/*
+ * `safe' write for sockets and pipes.
+ */
+file_private ssize_t
+swrite(int fd, const void *buf, size_t n)
+{
+ ssize_t rv;
+ size_t rn = n;
+
+ do
+ switch (rv = write(fd, buf, n)) {
+ case -1:
+ if (errno == EINTR)
+ continue;
+ return -1;
+ default:
+ n -= rv;
+ buf = CAST(const char *, buf) + rv;
+ break;
+ }
+ while (n > 0);
+ return rn;
+}
+
+
+/*
+ * `safe' read for sockets and pipes.
+ */
+file_protected ssize_t
+sread(int fd, void *buf, size_t n, int canbepipe __attribute__((__unused__)))
+{
+ ssize_t rv;
+#ifdef FIONREAD
+ int t = 0;
+#endif
+ size_t rn = n;
+
+ if (fd == STDIN_FILENO)
+ goto nocheck;
+
+#ifdef FIONREAD
+ if (canbepipe && (ioctl(fd, FIONREAD, &t) == -1 || t == 0)) {
+#ifdef FD_ZERO
+ ssize_t cnt;
+ for (cnt = 0;; cnt++) {
+ fd_set check;
+ struct timeval tout = {0, 100 * 1000};
+ int selrv;
+
+ FD_ZERO(&check);
+ FD_SET(fd, &check);
+
+ /*
+ * Avoid soft deadlock: do not read if there
+ * is nothing to read from sockets and pipes.
+ */
+ selrv = select(fd + 1, &check, NULL, NULL, &tout);
+ if (selrv == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ } else if (selrv == 0 && cnt >= 5) {
+ return 0;
+ } else
+ break;
+ }
+#endif
+ (void)ioctl(fd, FIONREAD, &t);
+ }
+
+ if (t > 0 && CAST(size_t, t) < n) {
+ n = t;
+ rn = n;
+ }
+#endif
+
+nocheck:
+ do
+ switch ((rv = read(fd, buf, n))) {
+ case -1:
+ if (errno == EINTR)
+ continue;
+ return -1;
+ case 0:
+ return rn - n;
+ default:
+ n -= rv;
+ buf = CAST(char *, CCAST(void *, buf)) + rv;
+ break;
+ }
+ while (n > 0);
+ return rn;
+}
+
+file_protected int
+file_pipe2file(struct magic_set *ms, int fd, const void *startbuf,
+ size_t nbytes)
+{
+ char buf[4096];
+ ssize_t r;
+ int tfd;
+
+#ifdef WIN32
+ const char *t;
+ buf[0] = '\0';
+ if ((t = getenv("TEMP")) != NULL)
+ (void)strlcpy(buf, t, sizeof(buf));
+ else if ((t = getenv("TMP")) != NULL)
+ (void)strlcpy(buf, t, sizeof(buf));
+ else if ((t = getenv("TMPDIR")) != NULL)
+ (void)strlcpy(buf, t, sizeof(buf));
+ if (buf[0] != '\0')
+ (void)strlcat(buf, "/", sizeof(buf));
+ (void)strlcat(buf, "file.XXXXXX", sizeof(buf));
+#else
+ (void)strlcpy(buf, "/tmp/file.XXXXXX", sizeof(buf));
+#endif
+#ifndef HAVE_MKSTEMP
+ {
+ char *ptr = mktemp(buf);
+ tfd = open(ptr, O_RDWR|O_TRUNC|O_EXCL|O_CREAT, 0600);
+ r = errno;
+ (void)unlink(ptr);
+ errno = r;
+ }
+#else
+ {
+ int te;
+ mode_t ou = umask(0);
+ tfd = mkstemp(buf);
+ (void)umask(ou);
+ te = errno;
+ (void)unlink(buf);
+ errno = te;
+ }
+#endif
+ if (tfd == -1) {
+ file_error(ms, errno,
+ "cannot create temporary file for pipe copy");
+ return -1;
+ }
+
+ if (swrite(tfd, startbuf, nbytes) != CAST(ssize_t, nbytes))
+ r = 1;
+ else {
+ while ((r = sread(fd, buf, sizeof(buf), 1)) > 0)
+ if (swrite(tfd, buf, CAST(size_t, r)) != r)
+ break;
+ }
+
+ switch (r) {
+ case -1:
+ file_error(ms, errno, "error copying from pipe to temp file");
+ return -1;
+ case 0:
+ break;
+ default:
+ file_error(ms, errno, "error while writing to temp file");
+ return -1;
+ }
+
+ /*
+ * We duplicate the file descriptor, because fclose on a
+ * tmpfile will delete the file, but any open descriptors
+ * can still access the phantom inode.
+ */
+ if ((fd = dup2(tfd, fd)) == -1) {
+ file_error(ms, errno, "could not dup descriptor for temp file");
+ return -1;
+ }
+ (void)close(tfd);
+ if (lseek(fd, CAST(off_t, 0), SEEK_SET) == CAST(off_t, -1)) {
+ file_badseek(ms);
+ return -1;
+ }
+ return fd;
+}
+#if HAVE_FORK
+#ifdef BUILTIN_DECOMPRESS
+
+#define FHCRC (1 << 1)
+#define FEXTRA (1 << 2)
+#define FNAME (1 << 3)
+#define FCOMMENT (1 << 4)
+
+
+file_private int
+uncompressgzipped(const unsigned char *old, unsigned char **newch,
+ size_t bytes_max, size_t *n, int extra __attribute__((__unused__)))
+{
+ unsigned char flg;
+ size_t data_start = 10;
+
+ if (*n < 4) {
+ goto err;
+ }
+
+ flg = old[3];
+
+ if (flg & FEXTRA) {
+ if (data_start + 1 >= *n)
+ goto err;
+ data_start += 2 + old[data_start] + old[data_start + 1] * 256;
+ }
+ if (flg & FNAME) {
+ while(data_start < *n && old[data_start])
+ data_start++;
+ data_start++;
+ }
+ if (flg & FCOMMENT) {
+ while(data_start < *n && old[data_start])
+ data_start++;
+ data_start++;
+ }
+ if (flg & FHCRC)
+ data_start += 2;
+
+ if (data_start >= *n)
+ goto err;
+
+ *n -= data_start;
+ old += data_start;
+ return uncompresszlib(old, newch, bytes_max, n, 0);
+err:
+ return makeerror(newch, n, "File too short");
+}
+
+file_private int
+uncompresszlib(const unsigned char *old, unsigned char **newch,
+ size_t bytes_max, size_t *n, int zlib)
+{
+ int rc;
+ z_stream z;
+
+ z.next_in = CCAST(Bytef *, old);
+ z.avail_in = CAST(uint32_t, *n);
+ z.next_out = *newch;
+ z.avail_out = CAST(unsigned int, bytes_max);
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ /* LINTED bug in header macro */
+ rc = zlib ? inflateInit(&z) : inflateInit2(&z, -15);
+ if (rc != Z_OK)
+ goto err;
+
+ rc = inflate(&z, Z_SYNC_FLUSH);
+ if (rc != Z_OK && rc != Z_STREAM_END) {
+ inflateEnd(&z);
+ goto err;
+ }
+
+ *n = CAST(size_t, z.total_out);
+ rc = inflateEnd(&z);
+ if (rc != Z_OK)
+ goto err;
+
+ /* let's keep the nul-terminate tradition */
+ (*newch)[*n] = '\0';
+
+ return OKDATA;
+err:
+ return makeerror(newch, n, "%s", z.msg ? z.msg : zError(rc));
+}
+#endif
+
+#ifdef BUILTIN_BZLIB
+file_private int
+uncompressbzlib(const unsigned char *old, unsigned char **newch,
+ size_t bytes_max, size_t *n, int extra __attribute__((__unused__)))
+{
+ int rc;
+ bz_stream bz;
+
+ memset(&bz, 0, sizeof(bz));
+ rc = BZ2_bzDecompressInit(&bz, 0, 0);
+ if (rc != BZ_OK)
+ goto err;
+
+ bz.next_in = CCAST(char *, RCAST(const char *, old));
+ bz.avail_in = CAST(uint32_t, *n);
+ bz.next_out = RCAST(char *, *newch);
+ bz.avail_out = CAST(unsigned int, bytes_max);
+
+ rc = BZ2_bzDecompress(&bz);
+ if (rc != BZ_OK && rc != BZ_STREAM_END) {
+ BZ2_bzDecompressEnd(&bz);
+ goto err;
+ }
+
+ /* Assume byte_max is within 32bit */
+ /* assert(bz.total_out_hi32 == 0); */
+ *n = CAST(size_t, bz.total_out_lo32);
+ rc = BZ2_bzDecompressEnd(&bz);
+ if (rc != BZ_OK)
+ goto err;
+
+ /* let's keep the nul-terminate tradition */
+ (*newch)[*n] = '\0';
+
+ return OKDATA;
+err:
+ return makeerror(newch, n, "bunzip error %d", rc);
+}
+#endif
+
+#ifdef BUILTIN_XZLIB
+file_private int
+uncompressxzlib(const unsigned char *old, unsigned char **newch,
+ size_t bytes_max, size_t *n, int extra __attribute__((__unused__)))
+{
+ int rc;
+ lzma_stream xz;
+
+ memset(&xz, 0, sizeof(xz));
+ rc = lzma_auto_decoder(&xz, UINT64_MAX, 0);
+ if (rc != LZMA_OK)
+ goto err;
+
+ xz.next_in = CCAST(const uint8_t *, old);
+ xz.avail_in = CAST(uint32_t, *n);
+ xz.next_out = RCAST(uint8_t *, *newch);
+ xz.avail_out = CAST(unsigned int, bytes_max);
+
+ rc = lzma_code(&xz, LZMA_RUN);
+ if (rc != LZMA_OK && rc != LZMA_STREAM_END) {
+ lzma_end(&xz);
+ goto err;
+ }
+
+ *n = CAST(size_t, xz.total_out);
+
+ lzma_end(&xz);
+
+ /* let's keep the nul-terminate tradition */
+ (*newch)[*n] = '\0';
+
+ return OKDATA;
+err:
+ return makeerror(newch, n, "unxz error %d", rc);
+}
+#endif
+
+#ifdef BUILTIN_ZSTDLIB
+file_private int
+uncompresszstd(const unsigned char *old, unsigned char **newch,
+ size_t bytes_max, size_t *n, int extra __attribute__((__unused__)))
+{
+ size_t rc;
+ ZSTD_DStream *zstd;
+ ZSTD_inBuffer in;
+ ZSTD_outBuffer out;
+
+ if ((zstd = ZSTD_createDStream()) == NULL) {
+ return makeerror(newch, n, "No ZSTD decompression stream, %s",
+ strerror(errno));
+ }
+
+ rc = ZSTD_DCtx_reset(zstd, ZSTD_reset_session_only);
+ if (ZSTD_isError(rc))
+ goto err;
+
+ in.src = CCAST(const void *, old);
+ in.size = *n;
+ in.pos = 0;
+ out.dst = RCAST(void *, *newch);
+ out.size = bytes_max;
+ out.pos = 0;
+
+ rc = ZSTD_decompressStream(zstd, &out, &in);
+ if (ZSTD_isError(rc))
+ goto err;
+
+ *n = out.pos;
+
+ ZSTD_freeDStream(zstd);
+
+ /* let's keep the nul-terminate tradition */
+ (*newch)[*n] = '\0';
+
+ return OKDATA;
+err:
+ ZSTD_freeDStream(zstd);
+ return makeerror(newch, n, "zstd error %d", ZSTD_getErrorCode(rc));
+}
+#endif
+
+#ifdef BUILTIN_LZLIB
+file_private int
+uncompresslzlib(const unsigned char *old, unsigned char **newch,
+ size_t bytes_max, size_t *n, int extra __attribute__((__unused__)))
+{
+ enum LZ_Errno err;
+ size_t old_remaining = *n;
+ size_t new_remaining = bytes_max;
+ size_t total_read = 0;
+ unsigned char *bufp;
+ struct LZ_Decoder *dec;
+
+ bufp = *newch;
+
+ dec = LZ_decompress_open();
+ if (!dec) {
+ return makeerror(newch, n, "unable to allocate LZ_Decoder");
+ }
+ if (LZ_decompress_errno(dec) != LZ_ok)
+ goto err;
+
+ for (;;) {
+ // LZ_decompress_read() stops at member boundaries, so we may
+ // have more than one successful read after writing all data
+ // we have.
+ if (old_remaining > 0) {
+ int wr = LZ_decompress_write(dec, old, old_remaining);
+ if (wr < 0)
+ goto err;
+ old_remaining -= wr;
+ old += wr;
+ }
+
+ int rd = LZ_decompress_read(dec, bufp, new_remaining);
+ if (rd > 0) {
+ new_remaining -= rd;
+ bufp += rd;
+ total_read += rd;
+ }
+
+ if (rd < 0 || LZ_decompress_errno(dec) != LZ_ok)
+ goto err;
+ if (new_remaining == 0)
+ break;
+ if (old_remaining == 0 && rd == 0)
+ break;
+ }
+
+ LZ_decompress_close(dec);
+ *n = total_read;
+
+ /* let's keep the nul-terminate tradition */
+ *bufp = '\0';
+
+ return OKDATA;
+err:
+ err = LZ_decompress_errno(dec);
+ LZ_decompress_close(dec);
+ return makeerror(newch, n, "lzlib error: %s", LZ_strerror(err));
+}
+#endif
+
+
+static int
+makeerror(unsigned char **buf, size_t *len, const char *fmt, ...)
+{
+ char *msg;
+ va_list ap;
+ int rv;
+
+ free(*buf);
+ va_start(ap, fmt);
+ rv = vasprintf(&msg, fmt, ap);
+ va_end(ap);
+ if (rv < 0) {
+ *buf = NULL;
+ *len = 0;
+ return NODATA;
+ }
+ *buf = RCAST(unsigned char *, msg);
+ *len = strlen(msg);
+ return ERRDATA;
+}
+
+static void
+closefd(int *fd, size_t i)
+{
+ if (fd[i] == -1)
+ return;
+ (void) close(fd[i]);
+ fd[i] = -1;
+}
+
+static void
+closep(int *fd)
+{
+ size_t i;
+ for (i = 0; i < 2; i++)
+ closefd(fd, i);
+}
+
+static void
+movedesc(void *v, int i, int fd)
+{
+ if (fd == i)
+ return; /* "no dup was necessary" */
+#ifdef HAVE_POSIX_SPAWNP
+ posix_spawn_file_actions_t *fa = RCAST(posix_spawn_file_actions_t *, v);
+ posix_spawn_file_actions_adddup2(fa, fd, i);
+ posix_spawn_file_actions_addclose(fa, fd);
+#else
+ if (dup2(fd, i) == -1) {
+ DPRINTF("dup(%d, %d) failed (%s)\n", fd, i, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ close(v ? fd : fd);
+#endif
+}
+
+static void
+closedesc(void *v, int fd)
+{
+#ifdef HAVE_POSIX_SPAWNP
+ posix_spawn_file_actions_t *fa = RCAST(posix_spawn_file_actions_t *, v);
+ posix_spawn_file_actions_addclose(fa, fd);
+#else
+ close(v ? fd : fd);
+#endif
+}
+
+static void
+handledesc(void *v, int fd, int fdp[3][2])
+{
+ if (fd != -1) {
+ (void) lseek(fd, CAST(off_t, 0), SEEK_SET);
+ movedesc(v, STDIN_FILENO, fd);
+ } else {
+ movedesc(v, STDIN_FILENO, fdp[STDIN_FILENO][0]);
+ if (fdp[STDIN_FILENO][1] > 2)
+ closedesc(v, fdp[STDIN_FILENO][1]);
+ }
+
+ file_clear_closexec(STDIN_FILENO);
+
+///FIXME: if one of the fdp[i][j] is 0 or 1, this can bomb spectacularly
+ movedesc(v, STDOUT_FILENO, fdp[STDOUT_FILENO][1]);
+ if (fdp[STDOUT_FILENO][0] > 2)
+ closedesc(v, fdp[STDOUT_FILENO][0]);
+
+ file_clear_closexec(STDOUT_FILENO);
+
+ movedesc(v, STDERR_FILENO, fdp[STDERR_FILENO][1]);
+ if (fdp[STDERR_FILENO][0] > 2)
+ closedesc(v, fdp[STDERR_FILENO][0]);
+
+ file_clear_closexec(STDERR_FILENO);
+}
+
+static pid_t
+writechild(int fd, const void *old, size_t n)
+{
+ pid_t pid;
+
+ /*
+ * fork again, to avoid blocking because both
+ * pipes filled
+ */
+ pid = fork();
+ if (pid == -1) {
+ DPRINTF("Fork failed (%s)\n", strerror(errno));
+ return -1;
+ }
+ if (pid == 0) {
+ /* child */
+ if (swrite(fd, old, n) != CAST(ssize_t, n)) {
+ DPRINTF("Write failed (%s)\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ exit(EXIT_SUCCESS);
+ }
+ /* parent */
+ return pid;
+}
+
+static ssize_t
+filter_error(unsigned char *ubuf, ssize_t n)
+{
+ char *p;
+ char *buf;
+
+ ubuf[n] = '\0';
+ buf = RCAST(char *, ubuf);
+ while (isspace(CAST(unsigned char, *buf)))
+ buf++;
+ DPRINTF("Filter error[[[%s]]]\n", buf);
+ if ((p = strchr(CAST(char *, buf), '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(CAST(char *, buf), ';')) != NULL)
+ *p = '\0';
+ if ((p = strrchr(CAST(char *, buf), ':')) != NULL) {
+ ++p;
+ while (isspace(CAST(unsigned char, *p)))
+ p++;
+ n = strlen(p);
+ memmove(ubuf, p, CAST(size_t, n + 1));
+ }
+ DPRINTF("Filter error after[[[%s]]]\n", (char *)ubuf);
+ if (islower(*ubuf))
+ *ubuf = toupper(*ubuf);
+ return n;
+}
+
+file_private const char *
+methodname(size_t method)
+{
+ switch (method) {
+#ifdef BUILTIN_DECOMPRESS
+ case METH_FROZEN:
+ case METH_ZLIB:
+ return "zlib";
+#endif
+#ifdef BUILTIN_BZLIB
+ case METH_BZIP:
+ return "bzlib";
+#endif
+#ifdef BUILTIN_XZLIB
+ case METH_XZ:
+ case METH_LZMA:
+ return "xzlib";
+#endif
+#ifdef BUILTIN_ZSTDLIB
+ case METH_ZSTD:
+ return "zstd";
+#endif
+#ifdef BUILTIN_LZLIB
+ case METH_LZIP:
+ return "lzlib";
+#endif
+ default:
+ return compr[method].argv[0];
+ }
+}
+
+file_private int (*
+getdecompressor(size_t method))(const unsigned char *, unsigned char **, size_t,
+ size_t *, int)
+{
+ switch (method) {
+#ifdef BUILTIN_DECOMPRESS
+ case METH_FROZEN:
+ return uncompressgzipped;
+ case METH_ZLIB:
+ return uncompresszlib;
+#endif
+#ifdef BUILTIN_BZLIB
+ case METH_BZIP:
+ return uncompressbzlib;
+#endif
+#ifdef BUILTIN_XZLIB
+ case METH_XZ:
+ case METH_LZMA:
+ return uncompressxzlib;
+#endif
+#ifdef BUILTIN_ZSTDLIB
+ case METH_ZSTD:
+ return uncompresszstd;
+#endif
+#ifdef BUILTIN_LZLIB
+ case METH_LZIP:
+ return uncompresslzlib;
+#endif
+ default:
+ return NULL;
+ }
+}
+
+file_private int
+uncompressbuf(int fd, size_t bytes_max, size_t method, int nofork,
+ const unsigned char *old, unsigned char **newch, size_t* n)
+{
+ int fdp[3][2];
+ int status, rv, w;
+ pid_t pid;
+ pid_t writepid = -1;
+ size_t i;
+ ssize_t r;
+ char *const *args;
+#ifdef HAVE_POSIX_SPAWNP
+ posix_spawn_file_actions_t fa;
+#endif
+ int (*decompress)(const unsigned char *, unsigned char **,
+ size_t, size_t *, int) = getdecompressor(method);
+
+ *newch = CAST(unsigned char *, malloc(bytes_max + 1));
+ if (*newch == NULL)
+ return makeerror(newch, n, "No buffer, %s", strerror(errno));
+
+ if (decompress) {
+ if (nofork) {
+ return makeerror(newch, n,
+ "Fork is required to uncompress, but disabled");
+ }
+ return (*decompress)(old, newch, bytes_max, n, 1);
+ }
+
+ (void)fflush(stdout);
+ (void)fflush(stderr);
+
+ for (i = 0; i < __arraycount(fdp); i++)
+ fdp[i][0] = fdp[i][1] = -1;
+
+ /*
+ * There are multithreaded users who run magic_file()
+ * from dozens of threads. If two parallel magic_file() calls
+ * analyze two large compressed files, both will spawn
+ * an uncompressing child here, which writes out uncompressed data.
+ * We read some portion, then close the pipe, then waitpid() the child.
+ * If uncompressed data is larger, child should get EPIPE and exit.
+ * However, with *parallel* calls OTHER child may unintentionally
+ * inherit pipe fds, thus keeping pipe open and making writes in
+ * our child block instead of failing with EPIPE!
+ * (For the bug to occur, two threads must mutually inherit their pipes,
+ * and both must have large outputs. Thus it happens not that often).
+ * To avoid this, be sure to create pipes with O_CLOEXEC.
+ */
+ if ((fd == -1 && file_pipe_closexec(fdp[STDIN_FILENO]) == -1) ||
+ file_pipe_closexec(fdp[STDOUT_FILENO]) == -1 ||
+ file_pipe_closexec(fdp[STDERR_FILENO]) == -1) {
+ closep(fdp[STDIN_FILENO]);
+ closep(fdp[STDOUT_FILENO]);
+ return makeerror(newch, n, "Cannot create pipe, %s",
+ strerror(errno));
+ }
+
+ args = RCAST(char *const *, RCAST(intptr_t, compr[method].argv));
+#ifdef HAVE_POSIX_SPAWNP
+ posix_spawn_file_actions_init(&fa);
+
+ handledesc(&fa, fd, fdp);
+
+ status = posix_spawnp(&pid, compr[method].argv[0], &fa, NULL,
+ args, NULL);
+
+ posix_spawn_file_actions_destroy(&fa);
+
+ if (status == -1) {
+ return makeerror(newch, n, "Cannot posix_spawn `%s', %s",
+ compr[method].argv[0], strerror(errno));
+ }
+#else
+ /* For processes with large mapped virtual sizes, vfork
+ * may be _much_ faster (10-100 times) than fork.
+ */
+ pid = vfork();
+ if (pid == -1) {
+ return makeerror(newch, n, "Cannot vfork, %s",
+ strerror(errno));
+ }
+ if (pid == 0) {
+ /* child */
+ /* Note: we are after vfork, do not modify memory
+ * in a way which confuses parent. In particular,
+ * do not modify fdp[i][j].
+ */
+ handledesc(NULL, fd, fdp);
+
+ (void)execvp(compr[method].argv[0], args);
+ dprintf(STDERR_FILENO, "exec `%s' failed, %s",
+ compr[method].argv[0], strerror(errno));
+ _exit(EXIT_FAILURE); /* _exit(), not exit(), because of vfork */
+ }
+#endif
+ /* parent */
+ /* Close write sides of child stdout/err pipes */
+ for (i = 1; i < __arraycount(fdp); i++)
+ closefd(fdp[i], 1);
+ /* Write the buffer data to child stdin, if we don't have fd */
+ if (fd == -1) {
+ closefd(fdp[STDIN_FILENO], 0);
+ writepid = writechild(fdp[STDIN_FILENO][1], old, *n);
+ if (writepid == (pid_t)-1) {
+ rv = makeerror(newch, n, "Write to child failed, %s",
+ strerror(errno));
+ goto err;
+ }
+ closefd(fdp[STDIN_FILENO], 1);
+ }
+
+ rv = OKDATA;
+ r = sread(fdp[STDOUT_FILENO][0], *newch, bytes_max, 0);
+ if (r < 0) {
+ rv = ERRDATA;
+ DPRINTF("Read stdout failed %d (%s)\n", fdp[STDOUT_FILENO][0],
+ strerror(errno));
+ goto err;
+ }
+ if (CAST(size_t, r) == bytes_max) {
+ /*
+ * close fd so that the child exits with sigpipe and ignore
+ * errors, otherwise we risk the child blocking and never
+ * exiting.
+ */
+ closefd(fdp[STDOUT_FILENO], 0);
+ goto ok;
+ }
+ if ((r = sread(fdp[STDERR_FILENO][0], *newch, bytes_max, 0)) > 0) {
+ rv = ERRDATA;
+ r = filter_error(*newch, r);
+ goto ok;
+ }
+ if (r == 0)
+ goto ok;
+ rv = makeerror(newch, n, "Read stderr failed, %s",
+ strerror(errno));
+ goto err;
+ok:
+ *n = r;
+ /* NUL terminate, as every buffer is handled here. */
+ (*newch)[*n] = '\0';
+err:
+ closefd(fdp[STDIN_FILENO], 1);
+ closefd(fdp[STDOUT_FILENO], 0);
+ closefd(fdp[STDERR_FILENO], 0);
+
+ w = waitpid(pid, &status, 0);
+wait_err:
+ if (w == -1) {
+ rv = makeerror(newch, n, "Wait failed, %s", strerror(errno));
+ DPRINTF("Child wait return %#x\n", status);
+ } else if (!WIFEXITED(status)) {
+ DPRINTF("Child not exited (%#x)\n", status);
+ } else if (WEXITSTATUS(status) != 0) {
+ DPRINTF("Child exited (%#x)\n", WEXITSTATUS(status));
+ }
+ if (writepid > 0) {
+ /* _After_ we know decompressor has exited, our input writer
+ * definitely will exit now (at worst, writing fails in it,
+ * since output fd is closed now on the reading size).
+ */
+ w = waitpid(writepid, &status, 0);
+ writepid = -1;
+ goto wait_err;
+ }
+
+ closefd(fdp[STDIN_FILENO], 0); //why? it is already closed here!
+ DPRINTF("Returning %p n=%" SIZE_T_FORMAT "u rv=%d\n", *newch, *n, rv);
+
+ return rv;
+}
+#endif
diff --git a/src/ctime_r.c b/src/ctime_r.c
new file mode 100644
index 0000000..60ff1c5
--- /dev/null
+++ b/src/ctime_r.c
@@ -0,0 +1,19 @@
+/* $File: ctime_r.c,v 1.3 2022/09/24 20:30:13 christos Exp $ */
+
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: ctime_r.c,v 1.3 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+#include <time.h>
+#include <string.h>
+
+/* ctime_r is not thread-safe anyway */
+char *
+ctime_r(const time_t *t, char *dst)
+{
+ char *p = ctime(t);
+ if (p == NULL)
+ return NULL;
+ memcpy(dst, p, 26);
+ return dst;
+}
diff --git a/src/der.c b/src/der.c
new file mode 100644
index 0000000..3a03651
--- /dev/null
+++ b/src/der.c
@@ -0,0 +1,458 @@
+/*-
+ * Copyright (c) 2016 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+/*
+ * DER (Distinguished Encoding Rules) Parser
+ *
+ * Sources:
+ * https://en.wikipedia.org/wiki/X.690
+ * http://fm4dd.com/openssl/certexamples.htm
+ * http://blog.engelke.com/2014/10/17/parsing-ber-and-der-encoded-asn-1-objects/
+ */
+#ifndef TEST_DER
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: der.c,v 1.27 2022/09/24 20:30:13 christos Exp $")
+#endif
+#else
+#define SIZE_T_FORMAT "z"
+#define CAST(a, b) ((a)(b))
+#endif
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef TEST_DER
+#include "magic.h"
+#include "der.h"
+#else
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <err.h>
+#endif
+
+#define DER_BAD CAST(uint32_t, -1)
+
+#define DER_CLASS_UNIVERSAL 0
+#define DER_CLASS_APPLICATION 1
+#define DER_CLASS_CONTEXT 2
+#define DER_CLASS_PRIVATE 3
+#if defined(DEBUG_DER) || defined(TEST_DER)
+static const char der_class[] = "UACP";
+#endif
+
+#define DER_TYPE_PRIMITIVE 0
+#define DER_TYPE_CONSTRUCTED 1
+#if defined(DEBUG_DER) || defined(TEST_DER)
+static const char der_type[] = "PC";
+#endif
+
+#define DER_TAG_EOC 0x00
+#define DER_TAG_BOOLEAN 0x01
+#define DER_TAG_INTEGER 0x02
+#define DER_TAG_BIT STRING 0x03
+#define DER_TAG_OCTET_STRING 0x04
+#define DER_TAG_NULL 0x05
+#define DER_TAG_OBJECT_IDENTIFIER 0x06
+#define DER_TAG_OBJECT_DESCRIPTOR 0x07
+#define DER_TAG_EXTERNAL 0x08
+#define DER_TAG_REAL 0x09
+#define DER_TAG_ENUMERATED 0x0a
+#define DER_TAG_EMBEDDED_PDV 0x0b
+#define DER_TAG_UTF8_STRING 0x0c
+#define DER_TAG_RELATIVE_OID 0x0d
+#define DER_TAG_TIME 0x0e
+#define DER_TAG_RESERVED_2 0x0f
+#define DER_TAG_SEQUENCE 0x10
+#define DER_TAG_SET 0x11
+#define DER_TAG_NUMERIC_STRING 0x12
+#define DER_TAG_PRINTABLE_STRING 0x13
+#define DER_TAG_T61_STRING 0x14
+#define DER_TAG_VIDEOTEX_STRING 0x15
+#define DER_TAG_IA5_STRING 0x16
+#define DER_TAG_UTCTIME 0x17
+#define DER_TAG_GENERALIZED_TIME 0x18
+#define DER_TAG_GRAPHIC_STRING 0x19
+#define DER_TAG_VISIBLE_STRING 0x1a
+#define DER_TAG_GENERAL_STRING 0x1b
+#define DER_TAG_UNIVERSAL_STRING 0x1c
+#define DER_TAG_CHARACTER_STRING 0x1d
+#define DER_TAG_BMP_STRING 0x1e
+#define DER_TAG_DATE 0x1f
+#define DER_TAG_TIME_OF_DAY 0x20
+#define DER_TAG_DATE_TIME 0x21
+#define DER_TAG_DURATION 0x22
+#define DER_TAG_OID_IRI 0x23
+#define DER_TAG_RELATIVE_OID_IRI 0x24
+#define DER_TAG_LAST 0x25
+
+static const char *der__tag[] = {
+ "eoc", "bool", "int", "bit_str", "octet_str",
+ "null", "obj_id", "obj_desc", "ext", "real",
+ "enum", "embed", "utf8_str", "rel_oid", "time",
+ "res2", "seq", "set", "num_str", "prt_str",
+ "t61_str", "vid_str", "ia5_str", "utc_time", "gen_time",
+ "gr_str", "vis_str", "gen_str", "univ_str", "char_str",
+ "bmp_str", "date", "tod", "datetime", "duration",
+ "oid-iri", "rel-oid-iri",
+};
+
+#ifdef DEBUG_DER
+#define DPRINTF(a) printf a
+#else
+#define DPRINTF(a)
+#endif
+
+#ifdef TEST_DER
+static uint8_t
+getclass(uint8_t c)
+{
+ return c >> 6;
+}
+
+static uint8_t
+gettype(uint8_t c)
+{
+ return (c >> 5) & 1;
+}
+#endif
+
+static uint32_t
+gettag(const uint8_t *c, size_t *p, size_t l)
+{
+ uint32_t tag;
+
+ if (*p >= l)
+ return DER_BAD;
+
+ tag = c[(*p)++] & 0x1f;
+
+ if (tag != 0x1f)
+ return tag;
+
+ if (*p >= l)
+ return DER_BAD;
+
+ while (c[*p] >= 0x80) {
+ tag = tag * 128 + c[(*p)++] - 0x80;
+ if (*p >= l)
+ return DER_BAD;
+ }
+ return tag;
+}
+
+/*
+ * Read the length of a DER tag from the input.
+ *
+ * `c` is the input, `p` is an output parameter that specifies how much of the
+ * input we consumed, and `l` is the maximum input length.
+ *
+ * Returns the length, or DER_BAD if the end of the input is reached or the
+ * length exceeds the remaining input.
+ */
+static uint32_t
+getlength(const uint8_t *c, size_t *p, size_t l)
+{
+ uint8_t digits, i;
+ size_t len;
+ int is_onebyte_result;
+
+ if (*p >= l) {
+ DPRINTF(("%s:[1] %zu >= %zu\n", __func__, *p, l));
+ return DER_BAD;
+ }
+
+ /*
+ * Digits can either be 0b0 followed by the result, or 0b1
+ * followed by the number of digits of the result. In either case,
+ * we verify that we can read so many bytes from the input.
+ */
+ is_onebyte_result = (c[*p] & 0x80) == 0;
+ digits = c[(*p)++] & 0x7f;
+ if (*p + digits >= l) {
+ DPRINTF(("%s:[2] %zu + %u >= %zu\n", __func__, *p, digits, l));
+ return DER_BAD;
+ }
+
+ if (is_onebyte_result)
+ return digits;
+
+ /*
+ * Decode len. We've already verified that we're allowed to read
+ * `digits` bytes.
+ */
+ len = 0;
+ for (i = 0; i < digits; i++)
+ len = (len << 8) | c[(*p)++];
+
+ if (len > UINT32_MAX - *p || *p + len > l) {
+ DPRINTF(("%s:[3] bad len %zu + %zu >= %zu\n",
+ __func__, *p, len, l));
+ return DER_BAD;
+ }
+ return CAST(uint32_t, len);
+}
+
+static const char *
+der_tag(char *buf, size_t len, uint32_t tag)
+{
+ if (tag < DER_TAG_LAST)
+ strlcpy(buf, der__tag[tag], len);
+ else
+ snprintf(buf, len, "%#x", tag);
+ return buf;
+}
+
+#ifndef TEST_DER
+static int
+der_data(char *buf, size_t blen, uint32_t tag, const void *q, uint32_t len)
+{
+ uint32_t i;
+ const uint8_t *d = CAST(const uint8_t *, q);
+ switch (tag) {
+ case DER_TAG_PRINTABLE_STRING:
+ case DER_TAG_UTF8_STRING:
+ case DER_TAG_IA5_STRING:
+ return snprintf(buf, blen, "%.*s", len, RCAST(const char *, q));
+ case DER_TAG_UTCTIME:
+ if (len < 12)
+ break;
+ return snprintf(buf, blen,
+ "20%c%c-%c%c-%c%c %c%c:%c%c:%c%c GMT", d[0], d[1], d[2],
+ d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11]);
+ default:
+ break;
+ }
+
+ for (i = 0; i < len; i++) {
+ uint32_t z = i << 1;
+ if (z < blen - 2)
+ snprintf(buf + z, blen - z, "%.2x", d[i]);
+ }
+ return len * 2;
+}
+
+int32_t
+der_offs(struct magic_set *ms, struct magic *m, size_t nbytes)
+{
+ const uint8_t *b = RCAST(const uint8_t *, ms->search.s);
+ size_t offs = 0, len = ms->search.s_len ? ms->search.s_len : nbytes;
+
+ if (gettag(b, &offs, len) == DER_BAD) {
+ DPRINTF(("%s: bad tag 1\n", __func__));
+ return -1;
+ }
+ DPRINTF(("%s1: %u %" SIZE_T_FORMAT "u %d\n", __func__, ms->offset,
+ offs, m->offset));
+
+ uint32_t tlen = getlength(b, &offs, len);
+ if (tlen == DER_BAD) {
+ DPRINTF(("%s: bad tag 2\n", __func__));
+ return -1;
+ }
+ DPRINTF(("%s2: %u %" SIZE_T_FORMAT "u %u\n", __func__, ms->offset,
+ offs, tlen));
+
+ offs += ms->offset + m->offset;
+ DPRINTF(("cont_level = %d\n", m->cont_level));
+#ifdef DEBUG_DER
+ size_t i;
+ for (i = 0; i < m->cont_level; i++)
+ printf("cont_level[%" SIZE_T_FORMAT "u] = %d\n", i,
+ ms->c.li[i].off);
+#endif
+ if (m->cont_level != 0) {
+ if (offs + tlen > nbytes)
+ return -1;
+ ms->c.li[m->cont_level - 1].off = CAST(int, offs + tlen);
+ DPRINTF(("cont_level[%u] = %d\n", m->cont_level - 1,
+ ms->c.li[m->cont_level - 1].off));
+ }
+ return CAST(int32_t, offs);
+}
+
+int
+der_cmp(struct magic_set *ms, struct magic *m)
+{
+ const uint8_t *b = RCAST(const uint8_t *, ms->search.s);
+ const char *s = m->value.s;
+ size_t offs = 0, len = ms->search.s_len;
+ uint32_t tag, tlen;
+ char buf[128];
+
+ DPRINTF(("%s: compare %zu bytes\n", __func__, len));
+
+ tag = gettag(b, &offs, len);
+ if (tag == DER_BAD) {
+ DPRINTF(("%s: bad tag 1\n", __func__));
+ return -1;
+ }
+
+ DPRINTF(("%s1: %d %" SIZE_T_FORMAT "u %d\n", __func__, ms->offset,
+ offs, m->offset));
+
+ tlen = getlength(b, &offs, len);
+ if (tlen == DER_BAD) {
+ DPRINTF(("%s: bad tag 2\n", __func__));
+ return -1;
+ }
+
+ der_tag(buf, sizeof(buf), tag);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "%s: tag %p got=%s exp=%s\n", __func__, b,
+ buf, s);
+ size_t slen = strlen(buf);
+
+ if (strncmp(buf, s, slen) != 0) {
+ DPRINTF(("%s: no string match %s != %s\n", __func__, buf, s));
+ return 0;
+ }
+
+ s += slen;
+
+again:
+ switch (*s) {
+ case '\0':
+ DPRINTF(("%s: EOF match\n", __func__));
+ return 1;
+ case '=':
+ s++;
+ goto val;
+ default:
+ if (!isdigit(CAST(unsigned char, *s))) {
+ DPRINTF(("%s: no digit %c\n", __func__, *s));
+ return 0;
+ }
+
+ slen = 0;
+ do
+ slen = slen * 10 + *s - '0';
+ while (isdigit(CAST(unsigned char, *++s)));
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "%s: len %" SIZE_T_FORMAT "u %u\n",
+ __func__, slen, tlen);
+ if (tlen != slen) {
+ DPRINTF(("%s: len %u != %zu\n", __func__, tlen, slen));
+ return 0;
+ }
+ goto again;
+ }
+val:
+ DPRINTF(("%s: before data %" SIZE_T_FORMAT "u %u\n", __func__, offs,
+ tlen));
+ der_data(buf, sizeof(buf), tag, b + offs, tlen);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "%s: data %s %s\n", __func__, buf, s);
+ if (strcmp(buf, s) != 0 && strcmp("x", s) != 0) {
+ DPRINTF(("%s: no string match %s != %s\n", __func__, buf, s));
+ return 0;
+ }
+ strlcpy(ms->ms_value.s, buf, sizeof(ms->ms_value.s));
+ DPRINTF(("%s: complete match\n", __func__));
+ return 1;
+}
+#endif
+
+#ifdef TEST_DER
+static void
+printtag(uint32_t tag, const void *q, uint32_t len)
+{
+ const uint8_t *d = q;
+ switch (tag) {
+ case DER_TAG_PRINTABLE_STRING:
+ case DER_TAG_UTF8_STRING:
+ case DER_TAG_IA5_STRING:
+ case DER_TAG_UTCTIME:
+ printf("%.*s\n", len, (const char *)q);
+ return;
+ default:
+ break;
+ }
+
+ for (uint32_t i = 0; i < len; i++)
+ printf("%.2x", d[i]);
+ printf("\n");
+}
+
+static void
+printdata(size_t level, const void *v, size_t x, size_t l)
+{
+ const uint8_t *p = v, *ep = p + l;
+ size_t ox;
+ char buf[128];
+
+ while (p + x < ep) {
+ const uint8_t *q;
+ uint8_t c = getclass(p[x]);
+ uint8_t t = gettype(p[x]);
+ ox = x;
+// if (x != 0)
+// printf("%.2x %.2x %.2x\n", p[x - 1], p[x], p[x + 1]);
+ uint32_t tag = gettag(p, &x, ep - p + x);
+ if (p + x >= ep)
+ break;
+ uint32_t len = getlength(p, &x, ep - p + x);
+
+ printf("%" SIZE_T_FORMAT "u %" SIZE_T_FORMAT "u-%"
+ SIZE_T_FORMAT "u %c,%c,%s,%u:", level, ox, x,
+ der_class[c], der_type[t],
+ der_tag(buf, sizeof(buf), tag), len);
+ q = p + x;
+ if (p + len > ep)
+ errx(EXIT_FAILURE, "corrupt der");
+ printtag(tag, q, len);
+ if (t != DER_TYPE_PRIMITIVE)
+ printdata(level + 1, p, x, len + x);
+ x += len;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct stat st;
+ size_t l;
+ void *p;
+
+ if ((fd = open(argv[1], O_RDONLY)) == -1)
+ err(EXIT_FAILURE, "open `%s'", argv[1]);
+ if (fstat(fd, &st) == -1)
+ err(EXIT_FAILURE, "stat `%s'", argv[1]);
+ l = (size_t)st.st_size;
+ if ((p = mmap(NULL, l, PROT_READ, MAP_FILE, fd, 0)) == MAP_FAILED)
+ err(EXIT_FAILURE, "mmap `%s'", argv[1]);
+
+ printdata(0, p, 0, l);
+ munmap(p, l);
+ return 0;
+}
+#endif
diff --git a/src/der.h b/src/der.h
new file mode 100644
index 0000000..3333239
--- /dev/null
+++ b/src/der.h
@@ -0,0 +1,28 @@
+/*-
+ * Copyright (c) 2016 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+extern int der_offs(struct magic_set *, struct magic *, size_t);
+extern int der_cmp(struct magic_set *, struct magic *);
diff --git a/src/dprintf.c b/src/dprintf.c
new file mode 100644
index 0000000..1b29dae
--- /dev/null
+++ b/src/dprintf.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: dprintf.c,v 1.4 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+int
+dprintf(int fd, const char *fmt, ...)
+{
+ va_list ap;
+ /* Simpler than using vasprintf() here, since we never need more */
+ char buf[1024];
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if ((size_t)len >= sizeof(buf))
+ return -1;
+
+ if (write(fd, buf, (size_t)len) != len)
+ return -1;
+
+ return len;
+}
diff --git a/src/elfclass.h b/src/elfclass.h
new file mode 100644
index 0000000..936d8dc
--- /dev/null
+++ b/src/elfclass.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Christos Zoulas 2008.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+ if (nbytes <= sizeof(elfhdr))
+ return 0;
+
+ u.l = 1;
+ (void)memcpy(&elfhdr, buf, sizeof elfhdr);
+ swap = (u.c[sizeof(int32_t) - 1] + 1) != elfhdr.e_ident[EI_DATA];
+
+ type = elf_getu16(swap, elfhdr.e_type);
+ notecount = ms->elf_notes_max;
+ switch (type) {
+#ifdef ELFCORE
+ case ET_CORE:
+ phnum = elf_getu16(swap, elfhdr.e_phnum);
+ if (phnum > ms->elf_phnum_max)
+ return toomany(ms, "program headers", phnum);
+ flags |= FLAGS_IS_CORE;
+ if (dophn_core(ms, clazz, swap, fd,
+ CAST(off_t, elf_getu(swap, elfhdr.e_phoff)), phnum,
+ CAST(size_t, elf_getu16(swap, elfhdr.e_phentsize)),
+ fsize, &flags, &notecount) == -1)
+ return -1;
+ break;
+#endif
+ case ET_EXEC:
+ case ET_DYN:
+ phnum = elf_getu16(swap, elfhdr.e_phnum);
+ if (phnum > ms->elf_phnum_max)
+ return toomany(ms, "program", phnum);
+ shnum = elf_getu16(swap, elfhdr.e_shnum);
+ if (shnum > ms->elf_shnum_max)
+ return toomany(ms, "section", shnum);
+ if (dophn_exec(ms, clazz, swap, fd,
+ CAST(off_t, elf_getu(swap, elfhdr.e_phoff)), phnum,
+ CAST(size_t, elf_getu16(swap, elfhdr.e_phentsize)),
+ fsize, shnum, &flags, &notecount) == -1)
+ return -1;
+ /*FALLTHROUGH*/
+ case ET_REL:
+ shnum = elf_getu16(swap, elfhdr.e_shnum);
+ if (shnum > ms->elf_shnum_max)
+ return toomany(ms, "section headers", shnum);
+ if (doshn(ms, clazz, swap, fd,
+ CAST(off_t, elf_getu(swap, elfhdr.e_shoff)), shnum,
+ CAST(size_t, elf_getu16(swap, elfhdr.e_shentsize)),
+ fsize, elf_getu16(swap, elfhdr.e_machine),
+ CAST(int, elf_getu16(swap, elfhdr.e_shstrndx)),
+ &flags, &notecount) == -1)
+ return -1;
+ break;
+
+ default:
+ break;
+ }
+ if (notecount == 0)
+ return toomany(ms, "notes", ms->elf_notes_max);
+ return 1;
diff --git a/src/encoding.c b/src/encoding.c
new file mode 100644
index 0000000..9dbb9dd
--- /dev/null
+++ b/src/encoding.c
@@ -0,0 +1,658 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * Encoding -- determine the character encoding of a text file.
+ *
+ * Joerg Wunsch <joerg@freebsd.org> wrote the original support for 8-bit
+ * international characters.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: encoding.c,v 1.42 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <string.h>
+#include <stdlib.h>
+
+
+file_private int looks_ascii(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private int looks_utf8_with_BOM(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private int looks_utf7(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private int looks_ucs16(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private int looks_ucs32(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private int looks_latin1(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private int looks_extended(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_private void from_ebcdic(const unsigned char *, size_t, unsigned char *);
+
+#ifdef DEBUG_ENCODING
+#define DPRINTF(a) printf a
+#else
+#define DPRINTF(a)
+#endif
+
+/*
+ * Try to determine whether text is in some character code we can
+ * identify. Each of these tests, if it succeeds, will leave
+ * the text converted into one-file_unichar_t-per-character Unicode in
+ * ubuf, and the number of characters converted in ulen.
+ */
+file_protected int
+file_encoding(struct magic_set *ms, const struct buffer *b,
+ file_unichar_t **ubuf, size_t *ulen, const char **code,
+ const char **code_mime, const char **type)
+{
+ const unsigned char *buf = CAST(const unsigned char *, b->fbuf);
+ size_t nbytes = b->flen;
+ size_t mlen;
+ int rv = 1, ucs_type;
+ file_unichar_t *udefbuf;
+ size_t udeflen;
+
+ if (ubuf == NULL)
+ ubuf = &udefbuf;
+ if (ulen == NULL)
+ ulen = &udeflen;
+
+ *type = "text";
+ *ulen = 0;
+ *code = "unknown";
+ *code_mime = "binary";
+
+ if (nbytes > ms->encoding_max)
+ nbytes = ms->encoding_max;
+
+ mlen = (nbytes + 1) * sizeof((*ubuf)[0]);
+ *ubuf = CAST(file_unichar_t *, calloc(CAST(size_t, 1), mlen));
+ if (*ubuf == NULL) {
+ file_oomem(ms, mlen);
+ goto done;
+ }
+ if (looks_ascii(buf, nbytes, *ubuf, ulen)) {
+ if (looks_utf7(buf, nbytes, *ubuf, ulen) > 0) {
+ DPRINTF(("utf-7 %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "Unicode text, UTF-7";
+ *code_mime = "utf-7";
+ } else {
+ DPRINTF(("ascii %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "ASCII";
+ *code_mime = "us-ascii";
+ }
+ } else if (looks_utf8_with_BOM(buf, nbytes, *ubuf, ulen) > 0) {
+ DPRINTF(("utf8/bom %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "Unicode text, UTF-8 (with BOM)";
+ *code_mime = "utf-8";
+ } else if (file_looks_utf8(buf, nbytes, *ubuf, ulen) > 1) {
+ DPRINTF(("utf8 %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "Unicode text, UTF-8";
+ *code_mime = "utf-8";
+ } else if ((ucs_type = looks_ucs32(buf, nbytes, *ubuf, ulen)) != 0) {
+ if (ucs_type == 1) {
+ *code = "Unicode text, UTF-32, little-endian";
+ *code_mime = "utf-32le";
+ } else {
+ *code = "Unicode text, UTF-32, big-endian";
+ *code_mime = "utf-32be";
+ }
+ DPRINTF(("ucs32 %" SIZE_T_FORMAT "u\n", *ulen));
+ } else if ((ucs_type = looks_ucs16(buf, nbytes, *ubuf, ulen)) != 0) {
+ if (ucs_type == 1) {
+ *code = "Unicode text, UTF-16, little-endian";
+ *code_mime = "utf-16le";
+ } else {
+ *code = "Unicode text, UTF-16, big-endian";
+ *code_mime = "utf-16be";
+ }
+ DPRINTF(("ucs16 %" SIZE_T_FORMAT "u\n", *ulen));
+ } else if (looks_latin1(buf, nbytes, *ubuf, ulen)) {
+ DPRINTF(("latin1 %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "ISO-8859";
+ *code_mime = "iso-8859-1";
+ } else if (looks_extended(buf, nbytes, *ubuf, ulen)) {
+ DPRINTF(("extended %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "Non-ISO extended-ASCII";
+ *code_mime = "unknown-8bit";
+ } else {
+ unsigned char *nbuf;
+
+ mlen = (nbytes + 1) * sizeof(nbuf[0]);
+ if ((nbuf = CAST(unsigned char *, malloc(mlen))) == NULL) {
+ file_oomem(ms, mlen);
+ goto done;
+ }
+ from_ebcdic(buf, nbytes, nbuf);
+
+ if (looks_ascii(nbuf, nbytes, *ubuf, ulen)) {
+ DPRINTF(("ebcdic %" SIZE_T_FORMAT "u\n", *ulen));
+ *code = "EBCDIC";
+ *code_mime = "ebcdic";
+ } else if (looks_latin1(nbuf, nbytes, *ubuf, ulen)) {
+ DPRINTF(("ebcdic/international %" SIZE_T_FORMAT "u\n",
+ *ulen));
+ *code = "International EBCDIC";
+ *code_mime = "ebcdic";
+ } else { /* Doesn't look like text at all */
+ DPRINTF(("binary\n"));
+ rv = 0;
+ *type = "binary";
+ }
+ free(nbuf);
+ }
+
+ done:
+ if (ubuf == &udefbuf)
+ free(udefbuf);
+
+ return rv;
+}
+
+/*
+ * This table reflects a particular philosophy about what constitutes
+ * "text," and there is room for disagreement about it.
+ *
+ * Version 3.31 of the file command considered a file to be ASCII if
+ * each of its characters was approved by either the isascii() or
+ * isalpha() function. On most systems, this would mean that any
+ * file consisting only of characters in the range 0x00 ... 0x7F
+ * would be called ASCII text, but many systems might reasonably
+ * consider some characters outside this range to be alphabetic,
+ * so the file command would call such characters ASCII. It might
+ * have been more accurate to call this "considered textual on the
+ * local system" than "ASCII."
+ *
+ * It considered a file to be "International language text" if each
+ * of its characters was either an ASCII printing character (according
+ * to the real ASCII standard, not the above test), a character in
+ * the range 0x80 ... 0xFF, or one of the following control characters:
+ * backspace, tab, line feed, vertical tab, form feed, carriage return,
+ * escape. No attempt was made to determine the language in which files
+ * of this type were written.
+ *
+ *
+ * The table below considers a file to be ASCII if all of its characters
+ * are either ASCII printing characters (again, according to the X3.4
+ * standard, not isascii()) or any of the following controls: bell,
+ * backspace, tab, line feed, form feed, carriage return, esc, nextline.
+ *
+ * I include bell because some programs (particularly shell scripts)
+ * use it literally, even though it is rare in normal text. I exclude
+ * vertical tab because it never seems to be used in real text. I also
+ * include, with hesitation, the X3.64/ECMA-43 control nextline (0x85),
+ * because that's what the dd EBCDIC->ASCII table maps the EBCDIC newline
+ * character to. It might be more appropriate to include it in the 8859
+ * set instead of the ASCII set, but it's got to be included in *something*
+ * we recognize or EBCDIC files aren't going to be considered textual.
+ * Some old Unix source files use SO/SI (^N/^O) to shift between Greek
+ * and Latin characters, so these should possibly be allowed. But they
+ * make a real mess on VT100-style displays if they're not paired properly,
+ * so we are probably better off not calling them text.
+ *
+ * A file is considered to be ISO-8859 text if its characters are all
+ * either ASCII, according to the above definition, or printing characters
+ * from the ISO-8859 8-bit extension, characters 0xA0 ... 0xFF.
+ *
+ * Finally, a file is considered to be international text from some other
+ * character code if its characters are all either ISO-8859 (according to
+ * the above definition) or characters in the range 0x80 ... 0x9F, which
+ * ISO-8859 considers to be control characters but the IBM PC and Macintosh
+ * consider to be printing characters.
+ */
+
+#define F 0 /* character never appears in text */
+#define T 1 /* character appears in plain ASCII text */
+#define I 2 /* character appears in ISO-8859 text */
+#define X 3 /* character appears in non-ISO extended ASCII (Mac, IBM PC) */
+
+file_private char text_chars[256] = {
+ /* BEL BS HT LF VT FF CR */
+ F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, /* 0x0X */
+ /* ESC */
+ F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, /* 0x1X */
+ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x2X */
+ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x3X */
+ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x4X */
+ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x5X */
+ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x6X */
+ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, /* 0x7X */
+ /* NEL */
+ X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, /* 0x8X */
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 0x9X */
+ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xaX */
+ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xbX */
+ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xcX */
+ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xdX */
+ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xeX */
+ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I /* 0xfX */
+};
+
+#define LOOKS(NAME, COND) \
+file_private int \
+looks_ ## NAME(const unsigned char *buf, size_t nbytes, file_unichar_t *ubuf, \
+ size_t *ulen) \
+{ \
+ size_t i; \
+\
+ *ulen = 0; \
+\
+ for (i = 0; i < nbytes; i++) { \
+ int t = text_chars[buf[i]]; \
+\
+ if (COND) \
+ return 0; \
+\
+ ubuf[(*ulen)++] = buf[i]; \
+ } \
+ return 1; \
+}
+
+LOOKS(ascii, t != T)
+LOOKS(latin1, t != T && t != I)
+LOOKS(extended, t != T && t != I && t != X)
+
+/*
+ * Decide whether some text looks like UTF-8. Returns:
+ *
+ * -1: invalid UTF-8
+ * 0: uses odd control characters, so doesn't look like text
+ * 1: 7-bit text
+ * 2: definitely UTF-8 text (valid high-bit set bytes)
+ *
+ * If ubuf is non-NULL on entry, text is decoded into ubuf, *ulen;
+ * ubuf must be big enough!
+ */
+
+// from: https://golang.org/src/unicode/utf8/utf8.go
+
+#define XX 0xF1 // invalid: size 1
+#define AS 0xF0 // ASCII: size 1
+#define S1 0x02 // accept 0, size 2
+#define S2 0x13 // accept 1, size 3
+#define S3 0x03 // accept 0, size 3
+#define S4 0x23 // accept 2, size 3
+#define S5 0x34 // accept 3, size 4
+#define S6 0x04 // accept 0, size 4
+#define S7 0x44 // accept 4, size 4
+
+#define LOCB 0x80
+#define HICB 0xBF
+
+// first is information about the first byte in a UTF-8 sequence.
+static const uint8_t first[] = {
+ // 1 2 3 4 5 6 7 8 9 A B C D E F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x00-0x0F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x10-0x1F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x20-0x2F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x30-0x3F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x40-0x4F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x50-0x5F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x60-0x6F
+ AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, AS, // 0x70-0x7F
+ // 1 2 3 4 5 6 7 8 9 A B C D E F
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0x80-0x8F
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0x90-0x9F
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0xA0-0xAF
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0xB0-0xBF
+ XX, XX, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, // 0xC0-0xCF
+ S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, S1, // 0xD0-0xDF
+ S2, S3, S3, S3, S3, S3, S3, S3, S3, S3, S3, S3, S3, S4, S3, S3, // 0xE0-0xEF
+ S5, S6, S6, S6, S7, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0xF0-0xFF
+};
+
+// acceptRange gives the range of valid values for the second byte in a UTF-8
+// sequence.
+struct accept_range {
+ uint8_t lo; // lowest value for second byte.
+ uint8_t hi; // highest value for second byte.
+} accept_ranges[16] = {
+// acceptRanges has size 16 to avoid bounds checks in the code that uses it.
+ { LOCB, HICB },
+ { 0xA0, HICB },
+ { LOCB, 0x9F },
+ { 0x90, HICB },
+ { LOCB, 0x8F },
+};
+
+file_protected int
+file_looks_utf8(const unsigned char *buf, size_t nbytes, file_unichar_t *ubuf,
+ size_t *ulen)
+{
+ size_t i;
+ int n;
+ file_unichar_t c;
+ int gotone = 0, ctrl = 0;
+
+ if (ubuf)
+ *ulen = 0;
+
+ for (i = 0; i < nbytes; i++) {
+ if ((buf[i] & 0x80) == 0) { /* 0xxxxxxx is plain ASCII */
+ /*
+ * Even if the whole file is valid UTF-8 sequences,
+ * still reject it if it uses weird control characters.
+ */
+
+ if (text_chars[buf[i]] != T)
+ ctrl = 1;
+
+ if (ubuf)
+ ubuf[(*ulen)++] = buf[i];
+ } else if ((buf[i] & 0x40) == 0) { /* 10xxxxxx never 1st byte */
+ return -1;
+ } else { /* 11xxxxxx begins UTF-8 */
+ int following;
+ uint8_t x = first[buf[i]];
+ const struct accept_range *ar =
+ &accept_ranges[(unsigned int)x >> 4];
+ if (x == XX)
+ return -1;
+
+ if ((buf[i] & 0x20) == 0) { /* 110xxxxx */
+ c = buf[i] & 0x1f;
+ following = 1;
+ } else if ((buf[i] & 0x10) == 0) { /* 1110xxxx */
+ c = buf[i] & 0x0f;
+ following = 2;
+ } else if ((buf[i] & 0x08) == 0) { /* 11110xxx */
+ c = buf[i] & 0x07;
+ following = 3;
+ } else if ((buf[i] & 0x04) == 0) { /* 111110xx */
+ c = buf[i] & 0x03;
+ following = 4;
+ } else if ((buf[i] & 0x02) == 0) { /* 1111110x */
+ c = buf[i] & 0x01;
+ following = 5;
+ } else
+ return -1;
+
+ for (n = 0; n < following; n++) {
+ i++;
+ if (i >= nbytes)
+ goto done;
+
+ if (n == 0 &&
+ (buf[i] < ar->lo || buf[i] > ar->hi))
+ return -1;
+
+ if ((buf[i] & 0x80) == 0 || (buf[i] & 0x40))
+ return -1;
+
+ c = (c << 6) + (buf[i] & 0x3f);
+ }
+
+ if (ubuf)
+ ubuf[(*ulen)++] = c;
+ gotone = 1;
+ }
+ }
+done:
+ return ctrl ? 0 : (gotone ? 2 : 1);
+}
+
+/*
+ * Decide whether some text looks like UTF-8 with BOM. If there is no
+ * BOM, return -1; otherwise return the result of looks_utf8 on the
+ * rest of the text.
+ */
+file_private int
+looks_utf8_with_BOM(const unsigned char *buf, size_t nbytes,
+ file_unichar_t *ubuf, size_t *ulen)
+{
+ if (nbytes > 3 && buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf)
+ return file_looks_utf8(buf + 3, nbytes - 3, ubuf, ulen);
+ else
+ return -1;
+}
+
+file_private int
+looks_utf7(const unsigned char *buf, size_t nbytes, file_unichar_t *ubuf,
+ size_t *ulen)
+{
+ if (nbytes > 4 && buf[0] == '+' && buf[1] == '/' && buf[2] == 'v')
+ switch (buf[3]) {
+ case '8':
+ case '9':
+ case '+':
+ case '/':
+ if (ubuf)
+ *ulen = 0;
+ return 1;
+ default:
+ return -1;
+ }
+ else
+ return -1;
+}
+
+#define UCS16_NOCHAR(c) ((c) >= 0xfdd0 && (c) <= 0xfdef)
+#define UCS16_HISURR(c) ((c) >= 0xd800 && (c) <= 0xdbff)
+#define UCS16_LOSURR(c) ((c) >= 0xdc00 && (c) <= 0xdfff)
+
+file_private int
+looks_ucs16(const unsigned char *bf, size_t nbytes, file_unichar_t *ubf,
+ size_t *ulen)
+{
+ int bigend;
+ uint32_t hi;
+ size_t i;
+
+ if (nbytes < 2)
+ return 0;
+
+ if (bf[0] == 0xff && bf[1] == 0xfe)
+ bigend = 0;
+ else if (bf[0] == 0xfe && bf[1] == 0xff)
+ bigend = 1;
+ else
+ return 0;
+
+ *ulen = 0;
+ hi = 0;
+
+ for (i = 2; i + 1 < nbytes; i += 2) {
+ uint32_t uc;
+
+ if (bigend)
+ uc = CAST(uint32_t,
+ bf[i + 1] | (CAST(file_unichar_t, bf[i]) << 8));
+ else
+ uc = CAST(uint32_t,
+ bf[i] | (CAST(file_unichar_t, bf[i + 1]) << 8));
+
+ uc &= 0xffff;
+
+ switch (uc) {
+ case 0xfffe:
+ case 0xffff:
+ return 0;
+ default:
+ if (UCS16_NOCHAR(uc))
+ return 0;
+ break;
+ }
+ if (hi) {
+ if (!UCS16_LOSURR(uc))
+ return 0;
+ uc = 0x10000 + 0x400 * (hi - 1) + (uc - 0xdc00);
+ hi = 0;
+ }
+ if (uc < 128 && text_chars[CAST(size_t, uc)] != T)
+ return 0;
+ ubf[(*ulen)++] = uc;
+ if (UCS16_HISURR(uc))
+ hi = uc - 0xd800 + 1;
+ if (UCS16_LOSURR(uc))
+ return 0;
+ }
+
+ return 1 + bigend;
+}
+
+file_private int
+looks_ucs32(const unsigned char *bf, size_t nbytes, file_unichar_t *ubf,
+ size_t *ulen)
+{
+ int bigend;
+ size_t i;
+
+ if (nbytes < 4)
+ return 0;
+
+ if (bf[0] == 0xff && bf[1] == 0xfe && bf[2] == 0 && bf[3] == 0)
+ bigend = 0;
+ else if (bf[0] == 0 && bf[1] == 0 && bf[2] == 0xfe && bf[3] == 0xff)
+ bigend = 1;
+ else
+ return 0;
+
+ *ulen = 0;
+
+ for (i = 4; i + 3 < nbytes; i += 4) {
+ /* XXX fix to properly handle chars > 65536 */
+
+ if (bigend)
+ ubf[(*ulen)++] = CAST(file_unichar_t, bf[i + 3])
+ | (CAST(file_unichar_t, bf[i + 2]) << 8)
+ | (CAST(file_unichar_t, bf[i + 1]) << 16)
+ | (CAST(file_unichar_t, bf[i]) << 24);
+ else
+ ubf[(*ulen)++] = CAST(file_unichar_t, bf[i + 0])
+ | (CAST(file_unichar_t, bf[i + 1]) << 8)
+ | (CAST(file_unichar_t, bf[i + 2]) << 16)
+ | (CAST(file_unichar_t, bf[i + 3]) << 24);
+
+ if (ubf[*ulen - 1] == 0xfffe)
+ return 0;
+ if (ubf[*ulen - 1] < 128 &&
+ text_chars[CAST(size_t, ubf[*ulen - 1])] != T)
+ return 0;
+ }
+
+ return 1 + bigend;
+}
+#undef F
+#undef T
+#undef I
+#undef X
+
+/*
+ * This table maps each EBCDIC character to an (8-bit extended) ASCII
+ * character, as specified in the rationale for the dd(1) command in
+ * draft 11.2 (September, 1991) of the POSIX P1003.2 standard.
+ *
+ * Unfortunately it does not seem to correspond exactly to any of the
+ * five variants of EBCDIC documented in IBM's _Enterprise Systems
+ * Architecture/390: Principles of Operation_, SA22-7201-06, Seventh
+ * Edition, July, 1999, pp. I-1 - I-4.
+ *
+ * Fortunately, though, all versions of EBCDIC, including this one, agree
+ * on most of the printing characters that also appear in (7-bit) ASCII.
+ * Of these, only '|', '!', '~', '^', '[', and ']' are in question at all.
+ *
+ * Fortunately too, there is general agreement that codes 0x00 through
+ * 0x3F represent control characters, 0x41 a nonbreaking space, and the
+ * remainder printing characters.
+ *
+ * This is sufficient to allow us to identify EBCDIC text and to distinguish
+ * between old-style and internationalized examples of text.
+ */
+
+file_private unsigned char ebcdic_to_ascii[] = {
+ 0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31,
+128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7,
+144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26,
+' ', 160, 161, 162, 163, 164, 165, 166, 167, 168, 213, '.', '<', '(', '+', '|',
+'&', 169, 170, 171, 172, 173, 174, 175, 176, 177, '!', '$', '*', ')', ';', '~',
+'-', '/', 178, 179, 180, 181, 182, 183, 184, 185, 203, ',', '%', '_', '>', '?',
+186, 187, 188, 189, 190, 191, 192, 193, 194, '`', ':', '#', '@', '\'','=', '"',
+195, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 196, 197, 198, 199, 200, 201,
+202, 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', '^', 204, 205, 206, 207, 208,
+209, 229, 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 210, 211, 212, '[', 214, 215,
+216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, ']', 230, 231,
+'{', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 232, 233, 234, 235, 236, 237,
+'}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 238, 239, 240, 241, 242, 243,
+'\\',159, 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 244, 245, 246, 247, 248, 249,
+'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 250, 251, 252, 253, 254, 255
+};
+
+#ifdef notdef
+/*
+ * The following EBCDIC-to-ASCII table may relate more closely to reality,
+ * or at least to modern reality. It comes from
+ *
+ * http://ftp.s390.ibm.com/products/oe/bpxqp9.html
+ *
+ * and maps the characters of EBCDIC code page 1047 (the code used for
+ * Unix-derived software on IBM's 390 systems) to the corresponding
+ * characters from ISO 8859-1.
+ *
+ * If this table is used instead of the above one, some of the special
+ * cases for the NEL character can be taken out of the code.
+ */
+
+file_private unsigned char ebcdic_1047_to_8859[] = {
+0x00,0x01,0x02,0x03,0x9C,0x09,0x86,0x7F,0x97,0x8D,0x8E,0x0B,0x0C,0x0D,0x0E,0x0F,
+0x10,0x11,0x12,0x13,0x9D,0x0A,0x08,0x87,0x18,0x19,0x92,0x8F,0x1C,0x1D,0x1E,0x1F,
+0x80,0x81,0x82,0x83,0x84,0x85,0x17,0x1B,0x88,0x89,0x8A,0x8B,0x8C,0x05,0x06,0x07,
+0x90,0x91,0x16,0x93,0x94,0x95,0x96,0x04,0x98,0x99,0x9A,0x9B,0x14,0x15,0x9E,0x1A,
+0x20,0xA0,0xE2,0xE4,0xE0,0xE1,0xE3,0xE5,0xE7,0xF1,0xA2,0x2E,0x3C,0x28,0x2B,0x7C,
+0x26,0xE9,0xEA,0xEB,0xE8,0xED,0xEE,0xEF,0xEC,0xDF,0x21,0x24,0x2A,0x29,0x3B,0x5E,
+0x2D,0x2F,0xC2,0xC4,0xC0,0xC1,0xC3,0xC5,0xC7,0xD1,0xA6,0x2C,0x25,0x5F,0x3E,0x3F,
+0xF8,0xC9,0xCA,0xCB,0xC8,0xCD,0xCE,0xCF,0xCC,0x60,0x3A,0x23,0x40,0x27,0x3D,0x22,
+0xD8,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0xAB,0xBB,0xF0,0xFD,0xFE,0xB1,
+0xB0,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0xAA,0xBA,0xE6,0xB8,0xC6,0xA4,
+0xB5,0x7E,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0xA1,0xBF,0xD0,0x5B,0xDE,0xAE,
+0xAC,0xA3,0xA5,0xB7,0xA9,0xA7,0xB6,0xBC,0xBD,0xBE,0xDD,0xA8,0xAF,0x5D,0xB4,0xD7,
+0x7B,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0xAD,0xF4,0xF6,0xF2,0xF3,0xF5,
+0x7D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0xB9,0xFB,0xFC,0xF9,0xFA,0xFF,
+0x5C,0xF7,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0xB2,0xD4,0xD6,0xD2,0xD3,0xD5,
+0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0xB3,0xDB,0xDC,0xD9,0xDA,0x9F
+};
+#endif
+
+/*
+ * Copy buf[0 ... nbytes-1] into out[], translating EBCDIC to ASCII.
+ */
+file_private void
+from_ebcdic(const unsigned char *buf, size_t nbytes, unsigned char *out)
+{
+ size_t i;
+
+ for (i = 0; i < nbytes; i++) {
+ out[i] = ebcdic_to_ascii[buf[i]];
+ }
+}
diff --git a/src/file.c b/src/file.c
new file mode 100644
index 0000000..c1c3fb9
--- /dev/null
+++ b/src/file.c
@@ -0,0 +1,856 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * file - find type of a file or files - main program.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: file.c,v 1.213 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#ifdef RESTORE_TIME
+# if (__COHERENT__ >= 0x420)
+# include <sys/utime.h>
+# else
+# ifdef USE_UTIMES
+# include <sys/time.h>
+# else
+# include <utime.h>
+# endif
+# endif
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* for read() */
+#endif
+#ifdef HAVE_WCHAR_H
+#include <wchar.h>
+#endif
+#ifdef HAVE_WCTYPE_H
+#include <wctype.h>
+#endif
+#if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH) && \
+ defined(HAVE_WCTYPE_H)
+#define FILE_WIDE_SUPPORT
+#else
+#include <ctype.h>
+#endif
+
+#if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION)
+# include <getopt.h>
+# ifndef HAVE_GETOPT_LONG
+int getopt_long(int, char * const *, const char *,
+ const struct option *, int *);
+# endif
+# else
+# include "mygetopt.h"
+#endif
+
+#ifdef S_IFLNK
+# define IFLNK_h "h"
+# define IFLNK_L "L"
+#else
+# define IFLNK_h ""
+# define IFLNK_L ""
+#endif
+
+#define FILE_FLAGS "bcCdE" IFLNK_h "ik" IFLNK_L "lNnprsSvzZ0"
+#define OPTSTRING "bcCde:Ef:F:hiklLm:nNpP:rsSvzZ0"
+
+# define USAGE \
+ "Usage: %s [-" FILE_FLAGS "] [--apple] [--extension] [--mime-encoding]\n" \
+ " [--mime-type] [-e <testname>] [-F <separator>] " \
+ " [-f <namefile>]\n" \
+ " [-m <magicfiles>] [-P <parameter=value>] [--exclude-quiet]\n" \
+ " <file> ...\n" \
+ " %s -C [-m <magicfiles>]\n" \
+ " %s [--help]\n"
+
+file_private int /* Global command-line options */
+ bflag = 0, /* brief output format */
+ nopad = 0, /* Don't pad output */
+ nobuffer = 0, /* Do not buffer stdout */
+ nulsep = 0; /* Append '\0' to the separator */
+
+file_private const char *separator = ":"; /* Default field separator */
+file_private const struct option long_options[] = {
+#define OPT_HELP 1
+#define OPT_APPLE 2
+#define OPT_EXTENSIONS 3
+#define OPT_MIME_TYPE 4
+#define OPT_MIME_ENCODING 5
+#define OPT_EXCLUDE_QUIET 6
+#define OPT(shortname, longname, opt, def, doc) \
+ {longname, opt, NULL, shortname},
+#define OPT_LONGONLY(longname, opt, def, doc, id) \
+ {longname, opt, NULL, id},
+#include "file_opts.h"
+#undef OPT
+#undef OPT_LONGONLY
+ {0, 0, NULL, 0}
+ };
+
+file_private const struct {
+ const char *name;
+ int value;
+} nv[] = {
+ { "apptype", MAGIC_NO_CHECK_APPTYPE },
+ { "ascii", MAGIC_NO_CHECK_ASCII },
+ { "cdf", MAGIC_NO_CHECK_CDF },
+ { "compress", MAGIC_NO_CHECK_COMPRESS },
+ { "csv", MAGIC_NO_CHECK_CSV },
+ { "elf", MAGIC_NO_CHECK_ELF },
+ { "encoding", MAGIC_NO_CHECK_ENCODING },
+ { "soft", MAGIC_NO_CHECK_SOFT },
+ { "tar", MAGIC_NO_CHECK_TAR },
+ { "json", MAGIC_NO_CHECK_JSON },
+ { "text", MAGIC_NO_CHECK_TEXT }, /* synonym for ascii */
+ { "tokens", MAGIC_NO_CHECK_TOKENS }, /* OBSOLETE: ignored for backwards compatibility */
+};
+
+file_private struct {
+ const char *name;
+ size_t value;
+ size_t def;
+ const char *desc;
+ int tag;
+ int set;
+} pm[] = {
+ { "bytes", 0, FILE_BYTES_MAX, "max bytes to look inside file",
+ MAGIC_PARAM_BYTES_MAX, 0 },
+ { "elf_notes", 0, FILE_ELF_NOTES_MAX, "max ELF notes processed",
+ MAGIC_PARAM_ELF_NOTES_MAX, 0 },
+ { "elf_phnum", 0, FILE_ELF_PHNUM_MAX, "max ELF prog sections processed",
+ MAGIC_PARAM_ELF_PHNUM_MAX, 0 },
+ { "elf_shnum", 0, FILE_ELF_SHNUM_MAX, "max ELF sections processed",
+ MAGIC_PARAM_ELF_SHNUM_MAX, 0 },
+ { "encoding", 0, FILE_ENCODING_MAX, "max bytes to scan for encoding",
+ MAGIC_PARAM_ENCODING_MAX, 0 },
+ { "indir", 0, FILE_INDIR_MAX, "recursion limit for indirection",
+ MAGIC_PARAM_INDIR_MAX, 0 },
+ { "name", 0, FILE_NAME_MAX, "use limit for name/use magic",
+ MAGIC_PARAM_NAME_MAX, 0 },
+ { "regex", 0, FILE_REGEX_MAX, "length limit for REGEX searches",
+ MAGIC_PARAM_REGEX_MAX, 0 },
+};
+
+file_private int posixly;
+
+#ifdef __dead
+__dead
+#endif
+file_private void usage(void);
+file_private void docprint(const char *, int);
+#ifdef __dead
+__dead
+#endif
+file_private void help(void);
+
+file_private int unwrap(struct magic_set *, const char *);
+file_private int process(struct magic_set *ms, const char *, int);
+file_private struct magic_set *load(const char *, int);
+file_private void setparam(const char *);
+file_private void applyparam(magic_t);
+
+
+/*
+ * main - parse arguments and handle options
+ */
+int
+main(int argc, char *argv[])
+{
+ int c;
+ size_t i, j, wid, nw;
+ int action = 0, didsomefiles = 0, errflg = 0;
+ int flags = 0, e = 0;
+#ifdef HAVE_LIBSECCOMP
+ int sandbox = 1;
+#endif
+ struct magic_set *magic = NULL;
+ int longindex;
+ const char *magicfile = NULL; /* where the magic is */
+ char *progname;
+
+ /* makes islower etc work for other langs */
+ (void)setlocale(LC_CTYPE, "");
+
+#ifdef __EMX__
+ /* sh-like wildcard expansion! Shouldn't hurt at least ... */
+ _wildcard(&argc, &argv);
+#endif
+
+ if ((progname = strrchr(argv[0], '/')) != NULL)
+ progname++;
+ else
+ progname = argv[0];
+
+ file_setprogname(progname);
+
+
+#ifdef S_IFLNK
+ posixly = getenv("POSIXLY_CORRECT") != NULL;
+ flags |= posixly ? MAGIC_SYMLINK : 0;
+#endif
+ while ((c = getopt_long(argc, argv, OPTSTRING, long_options,
+ &longindex)) != -1)
+ switch (c) {
+ case OPT_HELP:
+ help();
+ break;
+ case OPT_APPLE:
+ flags |= MAGIC_APPLE;
+ break;
+ case OPT_EXTENSIONS:
+ flags |= MAGIC_EXTENSION;
+ break;
+ case OPT_MIME_TYPE:
+ flags |= MAGIC_MIME_TYPE;
+ break;
+ case OPT_MIME_ENCODING:
+ flags |= MAGIC_MIME_ENCODING;
+ break;
+ case '0':
+ nulsep++;
+ break;
+ case 'b':
+ bflag++;
+ break;
+ case 'c':
+ action = FILE_CHECK;
+ break;
+ case 'C':
+ action = FILE_COMPILE;
+ break;
+ case 'd':
+ flags |= MAGIC_DEBUG|MAGIC_CHECK;
+ break;
+ case 'E':
+ flags |= MAGIC_ERROR;
+ break;
+ case 'e':
+ case OPT_EXCLUDE_QUIET:
+ for (i = 0; i < __arraycount(nv); i++)
+ if (strcmp(nv[i].name, optarg) == 0)
+ break;
+
+ if (i == __arraycount(nv)) {
+ if (c != OPT_EXCLUDE_QUIET)
+ errflg++;
+ } else
+ flags |= nv[i].value;
+ break;
+
+ case 'f':
+ if(action)
+ usage();
+ if (magic == NULL)
+ if ((magic = load(magicfile, flags)) == NULL)
+ return 1;
+ applyparam(magic);
+ e |= unwrap(magic, optarg);
+ ++didsomefiles;
+ break;
+ case 'F':
+ separator = optarg;
+ break;
+ case 'i':
+ flags |= MAGIC_MIME;
+ break;
+ case 'k':
+ flags |= MAGIC_CONTINUE;
+ break;
+ case 'l':
+ action = FILE_LIST;
+ break;
+ case 'm':
+ magicfile = optarg;
+ break;
+ case 'n':
+ ++nobuffer;
+ break;
+ case 'N':
+ ++nopad;
+ break;
+#if defined(HAVE_UTIME) || defined(HAVE_UTIMES)
+ case 'p':
+ flags |= MAGIC_PRESERVE_ATIME;
+ break;
+#endif
+ case 'P':
+ setparam(optarg);
+ break;
+ case 'r':
+ flags |= MAGIC_RAW;
+ break;
+ case 's':
+ flags |= MAGIC_DEVICES;
+ break;
+ case 'S':
+#ifdef HAVE_LIBSECCOMP
+ sandbox = 0;
+#endif
+ break;
+ case 'v':
+ if (magicfile == NULL)
+ magicfile = magic_getpath(magicfile, action);
+ (void)fprintf(stdout, "%s-%s\n", file_getprogname(),
+ VERSION);
+ (void)fprintf(stdout, "magic file from %s\n",
+ magicfile);
+#ifdef HAVE_LIBSECCOMP
+ (void)fprintf(stdout, "seccomp support included\n");
+#endif
+ return 0;
+ case 'z':
+ flags |= MAGIC_COMPRESS;
+ break;
+
+ case 'Z':
+ flags |= MAGIC_COMPRESS|MAGIC_COMPRESS_TRANSP;
+ break;
+#ifdef S_IFLNK
+ case 'L':
+ flags |= MAGIC_SYMLINK;
+ break;
+ case 'h':
+ flags &= ~MAGIC_SYMLINK;
+ break;
+#endif
+ case '?':
+ default:
+ errflg++;
+ break;
+ }
+
+ if (errflg) {
+ usage();
+ }
+ if (e)
+ return e;
+
+#ifdef HAVE_LIBSECCOMP
+#if 0
+ if (sandbox && enable_sandbox_basic() == -1)
+#else
+ if (sandbox && enable_sandbox_full() == -1)
+#endif
+ file_err(EXIT_FAILURE, "SECCOMP initialisation failed");
+ if (sandbox)
+ flags |= MAGIC_NO_COMPRESS_FORK;
+#endif /* HAVE_LIBSECCOMP */
+
+ if (MAGIC_VERSION != magic_version())
+ file_warnx("Compiled magic version [%d] "
+ "does not match with shared library magic version [%d]\n",
+ MAGIC_VERSION, magic_version());
+
+ switch(action) {
+ case FILE_CHECK:
+ case FILE_COMPILE:
+ case FILE_LIST:
+ /*
+ * Don't try to check/compile ~/.magic unless we explicitly
+ * ask for it.
+ */
+ magic = magic_open(flags|MAGIC_CHECK);
+ if (magic == NULL) {
+ file_warn("Can't create magic");
+ return 1;
+ }
+
+
+ switch(action) {
+ case FILE_CHECK:
+ c = magic_check(magic, magicfile);
+ break;
+ case FILE_COMPILE:
+ c = magic_compile(magic, magicfile);
+ break;
+ case FILE_LIST:
+ c = magic_list(magic, magicfile);
+ break;
+ default:
+ abort();
+ }
+ if (c == -1) {
+ file_warnx("%s", magic_error(magic));
+ e = 1;
+ goto out;
+ }
+ goto out;
+ default:
+ if (magic == NULL)
+ if ((magic = load(magicfile, flags)) == NULL)
+ return 1;
+ applyparam(magic);
+ }
+
+ if (optind == argc) {
+ if (!didsomefiles)
+ usage();
+ goto out;
+ }
+
+ for (wid = 0, j = CAST(size_t, optind); j < CAST(size_t, argc);
+ j++) {
+ nw = file_mbswidth(magic, argv[j]);
+ if (nw > wid)
+ wid = nw;
+ }
+
+ /*
+ * If bflag is only set twice, set it depending on
+ * number of files [this is undocumented, and subject to change]
+ */
+ if (bflag == 2) {
+ bflag = optind >= argc - 1;
+ }
+ for (; optind < argc; optind++)
+ e |= process(magic, argv[optind], wid);
+
+out:
+ if (!nobuffer)
+ e |= fflush(stdout) != 0;
+
+ if (magic)
+ magic_close(magic);
+ return e;
+}
+
+file_private void
+applyparam(magic_t magic)
+{
+ size_t i;
+
+ for (i = 0; i < __arraycount(pm); i++) {
+ if (!pm[i].set)
+ continue;
+ if (magic_setparam(magic, pm[i].tag, &pm[i].value) == -1)
+ file_err(EXIT_FAILURE, "Can't set %s", pm[i].name);
+ }
+}
+
+file_private void
+setparam(const char *p)
+{
+ size_t i;
+ char *s;
+
+ if ((s = CCAST(char *, strchr(p, '='))) == NULL)
+ goto badparm;
+
+ for (i = 0; i < __arraycount(pm); i++) {
+ if (strncmp(p, pm[i].name, s - p) != 0)
+ continue;
+ pm[i].value = atoi(s + 1);
+ pm[i].set = 1;
+ return;
+ }
+badparm:
+ file_errx(EXIT_FAILURE, "Unknown param %s", p);
+}
+
+file_private struct magic_set *
+/*ARGSUSED*/
+load(const char *magicfile, int flags)
+{
+ struct magic_set *magic = magic_open(flags);
+ const char *e;
+
+ if (magic == NULL) {
+ file_warn("Can't create magic");
+ return NULL;
+ }
+ if (magic_load(magic, magicfile) == -1) {
+ file_warn("%s", magic_error(magic));
+ magic_close(magic);
+ return NULL;
+ }
+ if ((e = magic_error(magic)) != NULL)
+ file_warn("%s", e);
+ return magic;
+}
+
+/*
+ * unwrap -- read a file of filenames, do each one.
+ */
+file_private int
+unwrap(struct magic_set *ms, const char *fn)
+{
+ FILE *f;
+ ssize_t len;
+ char *line = NULL;
+ size_t llen = 0;
+ int wid = 0, cwid;
+ int e = 0;
+ size_t fi = 0, fimax = 0;
+ char **flist = NULL;
+
+ if (strcmp("-", fn) == 0)
+ f = stdin;
+ else {
+ if ((f = fopen(fn, "r")) == NULL) {
+ file_warn("Cannot open `%s'", fn);
+ return 1;
+ }
+ }
+
+ while ((len = getline(&line, &llen, f)) > 0) {
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ cwid = file_mbswidth(ms, line);
+ if (nobuffer) {
+ e |= process(ms, line, cwid);
+ free(line);
+ line = NULL;
+ llen = 0;
+ continue;
+ }
+ if (cwid > wid)
+ wid = cwid;
+ if (fi >= fimax) {
+ fimax += 100;
+ char **nf = CAST(char **,
+ realloc(flist, fimax * sizeof(*flist)));
+ if (nf == NULL) {
+ file_err(EXIT_FAILURE,
+ "Cannot allocate memory for file list");
+ }
+ flist = nf;
+ }
+ flist[fi++] = line;
+ line = NULL;
+ llen = 0;
+ }
+
+ if (!nobuffer) {
+ fimax = fi;
+ for (fi = 0; fi < fimax; fi++) {
+ e |= process(ms, flist[fi], wid);
+ free(flist[fi]);
+ }
+ }
+ free(flist);
+
+ if (f != stdin)
+ (void)fclose(f);
+ return e;
+}
+
+file_private void
+file_octal(unsigned char c)
+{
+ (void)putc('\\', stdout);
+ (void)putc(((c >> 6) & 7) + '0', stdout);
+ (void)putc(((c >> 3) & 7) + '0', stdout);
+ (void)putc(((c >> 0) & 7) + '0', stdout);
+}
+
+file_private void
+fname_print(const char *inname)
+{
+ size_t n = strlen(inname);
+#ifdef FILE_WIDE_SUPPORT
+ mbstate_t state;
+ wchar_t nextchar;
+ size_t bytesconsumed;
+
+
+ (void)memset(&state, 0, sizeof(state));
+ while (n > 0) {
+ bytesconsumed = mbrtowc(&nextchar, inname, n, &state);
+ if (bytesconsumed == CAST(size_t, -1) ||
+ bytesconsumed == CAST(size_t, -2)) {
+ nextchar = *inname++;
+ n--;
+ (void)memset(&state, 0, sizeof(state));
+ file_octal(CAST(unsigned char, nextchar));
+ continue;
+ }
+ inname += bytesconsumed;
+ n -= bytesconsumed;
+ if (iswprint(nextchar)) {
+ printf("%lc", (wint_t)nextchar);
+ continue;
+ }
+ /* XXX: What if it is > 255? */
+ file_octal(CAST(unsigned char, nextchar));
+ }
+#else
+ size_t i;
+ for (i = 0; i < n; i++) {
+ unsigned char c = CAST(unsigned char, inname[i]);
+ if (isprint(c)) {
+ (void)putc(c, stdout);
+ continue;
+ }
+ file_octal(c);
+ }
+#endif
+}
+
+/*
+ * Called for each input file on the command line (or in a list of files)
+ */
+file_private int
+process(struct magic_set *ms, const char *inname, int wid)
+{
+ const char *type, c = nulsep > 1 ? '\0' : '\n';
+ int std_in = strcmp(inname, "-") == 0;
+ int haderror = 0;
+
+ if (wid > 0 && !bflag) {
+ const char *pname = std_in ? "/dev/stdin" : inname;
+ if ((ms->flags & MAGIC_RAW) == 0)
+ fname_print(pname);
+ else
+ (void)printf("%s", pname);
+ if (nulsep)
+ (void)putc('\0', stdout);
+ if (nulsep < 2) {
+ (void)printf("%s", separator);
+ (void)printf("%*s ", CAST(int, nopad ? 0
+ : (wid - file_mbswidth(ms, inname))), "");
+ }
+ }
+
+ type = magic_file(ms, std_in ? NULL : inname);
+
+ if (type == NULL) {
+ haderror |= printf("ERROR: %s%c", magic_error(ms), c);
+ } else {
+ haderror |= printf("%s%c", type, c) < 0;
+ }
+ if (nobuffer)
+ haderror |= fflush(stdout) != 0;
+ return haderror || type == NULL;
+}
+
+file_protected size_t
+file_mbswidth(struct magic_set *ms, const char *s)
+{
+ size_t width = 0;
+#ifdef FILE_WIDE_SUPPORT
+ size_t bytesconsumed, n;
+ mbstate_t state;
+ wchar_t nextchar;
+
+ (void)memset(&state, 0, sizeof(state));
+ n = strlen(s);
+
+ while (n > 0) {
+ bytesconsumed = mbrtowc(&nextchar, s, n, &state);
+ if (bytesconsumed == CAST(size_t, -1) ||
+ bytesconsumed == CAST(size_t, -2)) {
+ nextchar = *s;
+ bytesconsumed = 1;
+ (void)memset(&state, 0, sizeof(state));
+ width += 4;
+ } else {
+ int w = wcwidth(nextchar);
+ width += ((ms->flags & MAGIC_RAW) != 0
+ || iswprint(nextchar)) ? (w > 0 ? w : 1) : 4;
+ }
+
+ s += bytesconsumed, n -= bytesconsumed;
+ }
+#else
+ for (; *s; s++) {
+ width += (ms->flags & MAGIC_RAW) != 0
+ || isprint(CAST(unsigned char, *s)) ? 1 : 4;
+ }
+#endif
+ return width;
+}
+
+file_private void
+usage(void)
+{
+ const char *pn = file_getprogname();
+ (void)fprintf(stderr, USAGE, pn, pn, pn);
+ exit(EXIT_FAILURE);
+}
+
+file_private void
+defprint(int def)
+{
+ if (!def)
+ return;
+ if (((def & 1) && posixly) || ((def & 2) && !posixly))
+ (void)fprintf(stdout, " (default)");
+ (void)putc('\n', stdout);
+}
+
+file_private void
+docprint(const char *opts, int def)
+{
+ size_t i;
+ int comma, pad;
+ char *sp, *p;
+
+ p = CCAST(char *, strchr(opts, '%'));
+ if (p == NULL) {
+ (void)fprintf(stdout, "%s", opts);
+ defprint(def);
+ return;
+ }
+
+ for (sp = p - 1; sp > opts && *sp == ' '; sp--)
+ continue;
+
+ (void)printf("%.*s", CAST(int, p - opts), opts);
+ pad = (int)CAST(int, p - sp - 1);
+
+ switch (*++p) {
+ case 'e':
+ comma = 0;
+ for (i = 0; i < __arraycount(nv); i++) {
+ (void)printf("%s%s", comma++ ? ", " : "", nv[i].name);
+ if (i && i % 5 == 0 && i != __arraycount(nv) - 1) {
+ (void)printf(",\n%*s", pad, "");
+ comma = 0;
+ }
+ }
+ break;
+ case 'P':
+ for (i = 0; i < __arraycount(pm); i++) {
+ (void)printf("%9s %7zu %s", pm[i].name, pm[i].def,
+ pm[i].desc);
+ if (i != __arraycount(pm) - 1)
+ (void)printf("\n%*s", pad, "");
+ }
+ break;
+ default:
+ file_errx(EXIT_FAILURE, "Unknown escape `%c' in long options",
+ *p);
+ break;
+ }
+ (void)printf("%s", opts + (p - opts) + 1);
+
+}
+
+file_private void
+help(void)
+{
+ (void)fputs(
+"Usage: file [OPTION...] [FILE...]\n"
+"Determine type of FILEs.\n"
+"\n", stdout);
+#define OPT(shortname, longname, opt, def, doc) \
+ (void)printf(" -%c, --" longname, shortname), \
+ docprint(doc, def);
+#define OPT_LONGONLY(longname, opt, def, doc, id) \
+ (void)printf(" --" longname), \
+ docprint(doc, def);
+#include "file_opts.h"
+#undef OPT
+#undef OPT_LONGONLY
+ (void)printf("\nReport bugs to https://bugs.astron.com/\n");
+ exit(EXIT_SUCCESS);
+}
+
+file_private const char *file_progname;
+
+file_protected void
+file_setprogname(const char *progname)
+{
+ file_progname = progname;
+}
+
+file_protected const char *
+file_getprogname(void)
+{
+ return file_progname;
+}
+
+file_protected void
+file_err(int e, const char *fmt, ...)
+{
+ va_list ap;
+ int se = errno;
+
+ va_start(ap, fmt);
+ (void)fprintf(stderr, "%s: ", file_progname);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (se)
+ (void)fprintf(stderr, " (%s)\n", strerror(se));
+ else
+ fputc('\n', stderr);
+ exit(e);
+}
+
+file_protected void
+file_errx(int e, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)fprintf(stderr, "%s: ", file_progname);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ exit(e);
+}
+
+file_protected void
+file_warn(const char *fmt, ...)
+{
+ va_list ap;
+ int se = errno;
+
+ va_start(ap, fmt);
+ (void)fprintf(stderr, "%s: ", file_progname);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (se)
+ (void)fprintf(stderr, " (%s)\n", strerror(se));
+ else
+ fputc('\n', stderr);
+ errno = se;
+}
+
+file_protected void
+file_warnx(const char *fmt, ...)
+{
+ va_list ap;
+ int se = errno;
+
+ va_start(ap, fmt);
+ (void)fprintf(stderr, "%s: ", file_progname);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ errno = se;
+}
diff --git a/src/file.h b/src/file.h
new file mode 100644
index 0000000..1e1f1e7
--- /dev/null
+++ b/src/file.h
@@ -0,0 +1,712 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * file.h - definitions for file(1) program
+ * @(#)$File: file.h,v 1.241 2022/12/26 17:31:14 christos Exp $
+ */
+
+#ifndef __file_h__
+#define __file_h__
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+#ifdef _WIN32
+# ifdef PRIu32
+# ifdef _WIN64
+# define SIZE_T_FORMAT PRIu64
+# else
+# define SIZE_T_FORMAT PRIu32
+# endif
+# define INT64_T_FORMAT PRIi64
+# define INTMAX_T_FORMAT PRIiMAX
+# else
+# ifdef _WIN64
+# define SIZE_T_FORMAT "I64"
+# else
+# define SIZE_T_FORMAT ""
+# endif
+# define INT64_T_FORMAT "I64"
+# define INTMAX_T_FORMAT "I64"
+# endif
+#else
+# define SIZE_T_FORMAT "z"
+# define INT64_T_FORMAT "ll"
+# define INTMAX_T_FORMAT "j"
+#endif
+
+#include <stdio.h> /* Include that here, to make sure __P gets defined */
+#include <errno.h>
+#include <fcntl.h> /* For open and flags */
+#include <regex.h>
+#include <time.h>
+#include <sys/types.h>
+#ifndef WIN32
+#include <sys/param.h>
+#endif
+/* Do this here and now, because struct stat gets re-defined on solaris */
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <locale.h>
+#if defined(HAVE_XLOCALE_H)
+#include <xlocale.h>
+#endif
+
+#define ENABLE_CONDITIONALS
+
+#ifndef MAGIC
+#define MAGIC "/etc/magic"
+#endif
+
+#if defined(__EMX__) || defined (WIN32)
+#define PATHSEP ';'
+#else
+#define PATHSEP ':'
+#endif
+
+#define file_private static
+
+#if HAVE_VISIBILITY && !defined(WIN32)
+#define file_public __attribute__ ((__visibility__("default")))
+#ifndef file_protected
+#define file_protected __attribute__ ((__visibility__("hidden")))
+#endif
+#else
+#define file_public
+#ifndef file_protected
+#define file_protected
+#endif
+#endif
+
+#ifndef __arraycount
+#define __arraycount(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#ifndef __GNUC_PREREQ__
+#ifdef __GNUC__
+#define __GNUC_PREREQ__(x, y) \
+ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
+ (__GNUC__ > (x)))
+#else
+#define __GNUC_PREREQ__(x, y) 0
+#endif
+#endif
+
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(a)
+#endif
+#endif
+
+#ifndef MIN
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
+
+#ifndef FD_CLOEXEC
+# define FD_CLOEXEC 1
+#endif
+
+#define FILE_BADSIZE CAST(size_t, ~0ul)
+#define MAXDESC 64 /* max len of text description/MIME type */
+#define MAXMIME 80 /* max len of text MIME type */
+#define MAXstring 128 /* max len of "string" types */
+
+#define MAGICNO 0xF11E041C
+#define VERSIONNO 18
+#define FILE_MAGICSIZE 376
+
+#define FILE_GUID_SIZE sizeof("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
+
+#define FILE_LOAD 0
+#define FILE_CHECK 1
+#define FILE_COMPILE 2
+#define FILE_LIST 3
+
+typedef regex_t file_regex_t;
+
+struct buffer {
+ int fd;
+ struct stat st;
+ const void *fbuf;
+ size_t flen;
+ off_t eoff;
+ void *ebuf;
+ size_t elen;
+};
+
+union VALUETYPE {
+ uint8_t b;
+ uint16_t h;
+ uint32_t l;
+ uint64_t q;
+ uint8_t hs[2]; /* 2 bytes of a fixed-endian "short" */
+ uint8_t hl[4]; /* 4 bytes of a fixed-endian "long" */
+ uint8_t hq[8]; /* 8 bytes of a fixed-endian "quad" */
+ char s[MAXstring]; /* the search string or regex pattern */
+ unsigned char us[MAXstring];
+ uint64_t guid[2];
+ float f;
+ double d;
+};
+
+struct magic {
+ /* Word 1 */
+ uint16_t cont_level; /* level of ">" */
+ uint8_t flag;
+#define INDIR 0x01 /* if '(...)' appears */
+#define OFFADD 0x02 /* if '>&' or '>...(&' appears */
+#define INDIROFFADD 0x04 /* if '>&(' appears */
+#define UNSIGNED 0x08 /* comparison is unsigned */
+#define NOSPACE 0x10 /* suppress space character before output */
+#define BINTEST 0x20 /* test is for a binary type (set only
+ for top-level tests) */
+#define TEXTTEST 0x40 /* for passing to file_softmagic */
+#define OFFNEGATIVE 0x80 /* relative to the end of file */
+
+ uint8_t factor;
+
+ /* Word 2 */
+ uint8_t reln; /* relation (0=eq, '>'=gt, etc) */
+ uint8_t vallen; /* length of string value, if any */
+ uint8_t type; /* comparison type (FILE_*) */
+ uint8_t in_type; /* type of indirection */
+#define FILE_INVALID 0
+#define FILE_BYTE 1
+#define FILE_SHORT 2
+#define FILE_DEFAULT 3
+#define FILE_LONG 4
+#define FILE_STRING 5
+#define FILE_DATE 6
+#define FILE_BESHORT 7
+#define FILE_BELONG 8
+#define FILE_BEDATE 9
+#define FILE_LESHORT 10
+#define FILE_LELONG 11
+#define FILE_LEDATE 12
+#define FILE_PSTRING 13
+#define FILE_LDATE 14
+#define FILE_BELDATE 15
+#define FILE_LELDATE 16
+#define FILE_REGEX 17
+#define FILE_BESTRING16 18
+#define FILE_LESTRING16 19
+#define FILE_SEARCH 20
+#define FILE_MEDATE 21
+#define FILE_MELDATE 22
+#define FILE_MELONG 23
+#define FILE_QUAD 24
+#define FILE_LEQUAD 25
+#define FILE_BEQUAD 26
+#define FILE_QDATE 27
+#define FILE_LEQDATE 28
+#define FILE_BEQDATE 29
+#define FILE_QLDATE 30
+#define FILE_LEQLDATE 31
+#define FILE_BEQLDATE 32
+#define FILE_FLOAT 33
+#define FILE_BEFLOAT 34
+#define FILE_LEFLOAT 35
+#define FILE_DOUBLE 36
+#define FILE_BEDOUBLE 37
+#define FILE_LEDOUBLE 38
+#define FILE_BEID3 39
+#define FILE_LEID3 40
+#define FILE_INDIRECT 41
+#define FILE_QWDATE 42
+#define FILE_LEQWDATE 43
+#define FILE_BEQWDATE 44
+#define FILE_NAME 45
+#define FILE_USE 46
+#define FILE_CLEAR 47
+#define FILE_DER 48
+#define FILE_GUID 49
+#define FILE_OFFSET 50
+#define FILE_BEVARINT 51
+#define FILE_LEVARINT 52
+#define FILE_MSDOSDATE 53
+#define FILE_LEMSDOSDATE 54
+#define FILE_BEMSDOSDATE 55
+#define FILE_MSDOSTIME 56
+#define FILE_LEMSDOSTIME 57
+#define FILE_BEMSDOSTIME 58
+#define FILE_OCTAL 59
+#define FILE_NAMES_SIZE 60 /* size of array to contain all names */
+
+#define IS_STRING(t) \
+ ((t) == FILE_STRING || \
+ (t) == FILE_PSTRING || \
+ (t) == FILE_BESTRING16 || \
+ (t) == FILE_LESTRING16 || \
+ (t) == FILE_REGEX || \
+ (t) == FILE_SEARCH || \
+ (t) == FILE_INDIRECT || \
+ (t) == FILE_NAME || \
+ (t) == FILE_USE || \
+ (t) == FILE_OCTAL)
+
+#define FILE_FMT_NONE 0
+#define FILE_FMT_NUM 1 /* "cduxXi" */
+#define FILE_FMT_STR 2 /* "s" */
+#define FILE_FMT_QUAD 3 /* "ll" */
+#define FILE_FMT_FLOAT 4 /* "eEfFgG" */
+#define FILE_FMT_DOUBLE 5 /* "eEfFgG" */
+
+ /* Word 3 */
+ uint8_t in_op; /* operator for indirection */
+ uint8_t mask_op; /* operator for mask */
+#ifdef ENABLE_CONDITIONALS
+ uint8_t cond; /* conditional type */
+#else
+ uint8_t dummy;
+#endif
+ uint8_t factor_op;
+#define FILE_FACTOR_OP_PLUS '+'
+#define FILE_FACTOR_OP_MINUS '-'
+#define FILE_FACTOR_OP_TIMES '*'
+#define FILE_FACTOR_OP_DIV '/'
+#define FILE_FACTOR_OP_NONE '\0'
+
+#define FILE_OPS "&|^+-*/%"
+#define FILE_OPAND 0
+#define FILE_OPOR 1
+#define FILE_OPXOR 2
+#define FILE_OPADD 3
+#define FILE_OPMINUS 4
+#define FILE_OPMULTIPLY 5
+#define FILE_OPDIVIDE 6
+#define FILE_OPMODULO 7
+#define FILE_OPS_MASK 0x07 /* mask for above ops */
+#define FILE_UNUSED_1 0x08
+#define FILE_UNUSED_2 0x10
+#define FILE_OPSIGNED 0x20
+#define FILE_OPINVERSE 0x40
+#define FILE_OPINDIRECT 0x80
+
+#ifdef ENABLE_CONDITIONALS
+#define COND_NONE 0
+#define COND_IF 1
+#define COND_ELIF 2
+#define COND_ELSE 3
+#endif /* ENABLE_CONDITIONALS */
+
+ /* Word 4 */
+ int32_t offset; /* offset to magic number */
+ /* Word 5 */
+ int32_t in_offset; /* offset from indirection */
+ /* Word 6 */
+ uint32_t lineno; /* line number in magic file */
+ /* Word 7,8 */
+ union {
+ uint64_t _mask; /* for use with numeric and date types */
+ struct {
+ uint32_t _count; /* repeat/line count */
+ uint32_t _flags; /* modifier flags */
+ } _s; /* for use with string types */
+ } _u;
+#define num_mask _u._mask
+#define str_range _u._s._count
+#define str_flags _u._s._flags
+ /* Words 9-24 */
+ union VALUETYPE value; /* either number or string */
+ /* Words 25-40 */
+ char desc[MAXDESC]; /* description */
+ /* Words 41-60 */
+ char mimetype[MAXMIME]; /* MIME type */
+ /* Words 61-62 */
+ char apple[8]; /* APPLE CREATOR/TYPE */
+ /* Words 63-78 */
+ char ext[64]; /* Popular extensions */
+};
+
+#define BIT(A) (1 << (A))
+#define STRING_COMPACT_WHITESPACE BIT(0)
+#define STRING_COMPACT_OPTIONAL_WHITESPACE BIT(1)
+#define STRING_IGNORE_LOWERCASE BIT(2)
+#define STRING_IGNORE_UPPERCASE BIT(3)
+#define REGEX_OFFSET_START BIT(4)
+#define STRING_TEXTTEST BIT(5)
+#define STRING_BINTEST BIT(6)
+#define PSTRING_1_BE BIT(7)
+#define PSTRING_1_LE BIT(7)
+#define PSTRING_2_BE BIT(8)
+#define PSTRING_2_LE BIT(9)
+#define PSTRING_4_BE BIT(10)
+#define PSTRING_4_LE BIT(11)
+#define REGEX_LINE_COUNT BIT(11)
+#define PSTRING_LEN \
+ (PSTRING_1_BE|PSTRING_2_LE|PSTRING_2_BE|PSTRING_4_LE|PSTRING_4_BE)
+#define PSTRING_LENGTH_INCLUDES_ITSELF BIT(12)
+#define STRING_TRIM BIT(13)
+#define STRING_FULL_WORD BIT(14)
+#define CHAR_COMPACT_WHITESPACE 'W'
+#define CHAR_COMPACT_OPTIONAL_WHITESPACE 'w'
+#define CHAR_IGNORE_LOWERCASE 'c'
+#define CHAR_IGNORE_UPPERCASE 'C'
+#define CHAR_REGEX_OFFSET_START 's'
+#define CHAR_TEXTTEST 't'
+#define CHAR_TRIM 'T'
+#define CHAR_FULL_WORD 'f'
+#define CHAR_BINTEST 'b'
+#define CHAR_PSTRING_1_BE 'B'
+#define CHAR_PSTRING_1_LE 'B'
+#define CHAR_PSTRING_2_BE 'H'
+#define CHAR_PSTRING_2_LE 'h'
+#define CHAR_PSTRING_4_BE 'L'
+#define CHAR_PSTRING_4_LE 'l'
+#define CHAR_PSTRING_LENGTH_INCLUDES_ITSELF 'J'
+#define STRING_IGNORE_CASE (STRING_IGNORE_LOWERCASE|STRING_IGNORE_UPPERCASE)
+#define STRING_DEFAULT_RANGE 100
+
+#define INDIRECT_RELATIVE BIT(0)
+#define CHAR_INDIRECT_RELATIVE 'r'
+
+/* list of magic entries */
+struct mlist {
+ struct magic *magic; /* array of magic entries */
+ file_regex_t **magic_rxcomp; /* array of compiled regexps */
+ size_t nmagic; /* number of entries in array */
+ void *map; /* internal resources used by entry */
+ struct mlist *next, *prev;
+};
+
+#ifdef __cplusplus
+#define CAST(T, b) static_cast<T>(b)
+#define RCAST(T, b) reinterpret_cast<T>(b)
+#define CCAST(T, b) const_cast<T>(b)
+#else
+#define CAST(T, b) ((T)(b))
+#define RCAST(T, b) ((T)(uintptr_t)(b))
+#define CCAST(T, b) ((T)(uintptr_t)(b))
+#endif
+
+struct level_info {
+ int32_t off;
+ int got_match;
+#ifdef ENABLE_CONDITIONALS
+ int last_match;
+ int last_cond; /* used for error checking by parse() */
+#endif
+};
+
+struct cont {
+ size_t len;
+ struct level_info *li;
+};
+
+#define MAGIC_SETS 2
+
+struct magic_set {
+ struct mlist *mlist[MAGIC_SETS]; /* list of regular entries */
+ struct cont c;
+ struct out {
+ char *buf; /* Accumulation buffer */
+ size_t blen; /* Length of buffer */
+ char *pbuf; /* Printable buffer */
+ } o;
+ uint32_t offset; /* a copy of m->offset while we */
+ /* are working on the magic entry */
+ uint32_t eoffset; /* offset from end of file */
+ int error;
+ int flags; /* Control magic tests. */
+ int event_flags; /* Note things that happened. */
+#define EVENT_HAD_ERR 0x01
+ const char *file;
+ size_t line; /* current magic line number */
+ mode_t mode; /* copy of current stat mode */
+
+ /* data for searches */
+ struct {
+ const char *s; /* start of search in original source */
+ size_t s_len; /* length of search region */
+ size_t offset; /* starting offset in source: XXX - should this be off_t? */
+ size_t rm_len; /* match length */
+ } search;
+
+ /* FIXME: Make the string dynamically allocated so that e.g.
+ strings matched in files can be longer than MAXstring */
+ union VALUETYPE ms_value; /* either number or string */
+ uint16_t indir_max;
+ uint16_t name_max;
+ uint16_t elf_shnum_max;
+ uint16_t elf_phnum_max;
+ uint16_t elf_notes_max;
+ uint16_t regex_max;
+ size_t bytes_max; /* number of bytes to read from file */
+ size_t encoding_max; /* bytes to look for encoding */
+#ifndef FILE_BYTES_MAX
+# define FILE_BYTES_MAX (7 * 1024 * 1024)/* how much of the file to look at */
+#endif /* above 0x6ab0f4 map offset for HelveticaNeue.dfont */
+#define FILE_ELF_NOTES_MAX 256
+#define FILE_ELF_PHNUM_MAX 2048
+#define FILE_ELF_SHNUM_MAX 32768
+#define FILE_INDIR_MAX 50
+#define FILE_NAME_MAX 50
+#define FILE_REGEX_MAX 8192
+#define FILE_ENCODING_MAX (64 * 1024)
+#if defined(HAVE_NEWLOCALE) && defined(HAVE_USELOCALE) && defined(HAVE_FREELOCALE)
+#define USE_C_LOCALE
+ locale_t c_lc_ctype;
+#define file_locale_used
+#else
+#define file_locale_used __attribute__((__unused__))
+#endif
+};
+
+/* Type for Unicode characters */
+typedef unsigned long file_unichar_t;
+
+struct stat;
+#define FILE_T_LOCAL 1
+#define FILE_T_WINDOWS 2
+file_protected const char *file_fmtdatetime(char *, size_t, uint64_t, int);
+file_protected const char *file_fmtdate(char *, size_t, uint16_t);
+file_protected const char *file_fmttime(char *, size_t, uint16_t);
+file_protected const char *file_fmtvarint(char *, size_t, const unsigned char *,
+ int);
+file_protected const char *file_fmtnum(char *, size_t, const char *, int);
+file_protected struct magic_set *file_ms_alloc(int);
+file_protected void file_ms_free(struct magic_set *);
+file_protected int file_default(struct magic_set *, size_t);
+file_protected int file_buffer(struct magic_set *, int, struct stat *, const char *,
+ const void *, size_t);
+file_protected int file_fsmagic(struct magic_set *, const char *, struct stat *);
+file_protected int file_pipe2file(struct magic_set *, int, const void *, size_t);
+file_protected int file_vprintf(struct magic_set *, const char *, va_list)
+ __attribute__((__format__(__printf__, 2, 0)));
+file_protected int file_separator(struct magic_set *);
+file_protected char *file_copystr(char *, size_t, size_t, const char *);
+file_protected int file_checkfmt(char *, size_t, const char *);
+file_protected size_t file_printedlen(const struct magic_set *);
+file_protected int file_print_guid(char *, size_t, const uint64_t *);
+file_protected int file_parse_guid(const char *, uint64_t *);
+file_protected int file_replace(struct magic_set *, const char *, const char *);
+file_protected int file_printf(struct magic_set *, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+file_protected int file_reset(struct magic_set *, int);
+file_protected int file_tryelf(struct magic_set *, const struct buffer *);
+file_protected int file_trycdf(struct magic_set *, const struct buffer *);
+#if HAVE_FORK
+file_protected int file_zmagic(struct magic_set *, const struct buffer *,
+ const char *);
+#endif
+file_protected int file_ascmagic(struct magic_set *, const struct buffer *,
+ int);
+file_protected int file_ascmagic_with_encoding(struct magic_set *,
+ const struct buffer *, file_unichar_t *, size_t, const char *, const char *, int);
+file_protected int file_encoding(struct magic_set *, const struct buffer *,
+ file_unichar_t **, size_t *, const char **, const char **, const char **);
+file_protected int file_is_json(struct magic_set *, const struct buffer *);
+file_protected int file_is_csv(struct magic_set *, const struct buffer *, int);
+file_protected int file_is_tar(struct magic_set *, const struct buffer *);
+file_protected int file_softmagic(struct magic_set *, const struct buffer *,
+ uint16_t *, uint16_t *, int, int);
+file_protected int file_apprentice(struct magic_set *, const char *, int);
+file_protected int buffer_apprentice(struct magic_set *, struct magic **,
+ size_t *, size_t);
+file_protected int file_magicfind(struct magic_set *, const char *, struct mlist *);
+file_protected uint64_t file_signextend(struct magic_set *, struct magic *,
+ uint64_t);
+file_protected uintmax_t file_varint2uintmax_t(const unsigned char *, int, size_t *);
+
+file_protected void file_badread(struct magic_set *);
+file_protected void file_badseek(struct magic_set *);
+file_protected void file_oomem(struct magic_set *, size_t);
+file_protected void file_error(struct magic_set *, int, const char *, ...)
+ __attribute__((__format__(__printf__, 3, 4)));
+file_protected void file_magerror(struct magic_set *, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+file_protected void file_magwarn(struct magic_set *, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+file_protected void file_mdump(struct magic *);
+file_protected void file_showstr(FILE *, const char *, size_t);
+file_protected size_t file_mbswidth(struct magic_set *, const char *);
+file_protected const char *file_getbuffer(struct magic_set *);
+file_protected ssize_t sread(int, void *, size_t, int);
+file_protected int file_check_mem(struct magic_set *, unsigned int);
+file_protected int file_looks_utf8(const unsigned char *, size_t, file_unichar_t *,
+ size_t *);
+file_protected size_t file_pstring_length_size(struct magic_set *,
+ const struct magic *);
+file_protected size_t file_pstring_get_length(struct magic_set *,
+ const struct magic *, const char *);
+file_protected char * file_printable(struct magic_set *, char *, size_t,
+ const char *, size_t);
+#ifdef __EMX__
+file_protected int file_os2_apptype(struct magic_set *, const char *, const void *,
+ size_t);
+#endif /* __EMX__ */
+file_protected int file_pipe_closexec(int *);
+file_protected int file_clear_closexec(int);
+file_protected char *file_strtrim(char *);
+
+file_protected void buffer_init(struct buffer *, int, const struct stat *,
+ const void *, size_t);
+file_protected void buffer_fini(struct buffer *);
+file_protected int buffer_fill(const struct buffer *);
+
+
+
+file_protected int file_regcomp(struct magic_set *, file_regex_t *, const char *,
+ int);
+file_protected int file_regexec(struct magic_set *, file_regex_t *, const char *,
+ size_t, regmatch_t *, int);
+file_protected void file_regfree(file_regex_t *);
+
+typedef struct {
+ char *buf;
+ size_t blen;
+ uint32_t offset;
+} file_pushbuf_t;
+
+file_protected file_pushbuf_t *file_push_buffer(struct magic_set *);
+file_protected char *file_pop_buffer(struct magic_set *, file_pushbuf_t *);
+
+#ifndef COMPILE_ONLY
+extern const char *file_names[];
+extern const size_t file_nnames;
+#endif
+
+#ifndef HAVE_PREAD
+ssize_t pread(int, void *, size_t, off_t);
+#endif
+#ifndef HAVE_VASPRINTF
+int vasprintf(char **, const char *, va_list);
+#endif
+#ifndef HAVE_ASPRINTF
+int asprintf(char **, const char *, ...);
+#endif
+#ifndef HAVE_DPRINTF
+int dprintf(int, const char *, ...);
+#endif
+
+#ifndef HAVE_STRLCPY
+size_t strlcpy(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRLCAT
+size_t strlcat(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRCASESTR
+char *strcasestr(const char *, const char *);
+#endif
+#ifndef HAVE_GETLINE
+ssize_t getline(char **, size_t *, FILE *);
+ssize_t getdelim(char **, size_t *, int, FILE *);
+#endif
+#ifndef HAVE_CTIME_R
+char *ctime_r(const time_t *, char *);
+#endif
+#ifndef HAVE_ASCTIME_R
+char *asctime_r(const struct tm *, char *);
+#endif
+#ifndef HAVE_GMTIME_R
+struct tm *gmtime_r(const time_t *, struct tm *);
+#endif
+#ifndef HAVE_LOCALTIME_R
+struct tm *localtime_r(const time_t *, struct tm *);
+#endif
+#ifndef HAVE_FMTCHECK
+const char *fmtcheck(const char *, const char *)
+ __attribute__((__format_arg__(2)));
+#endif
+
+#ifdef HAVE_LIBSECCOMP
+// basic filter
+// this mode should not interfere with normal operations
+// only some dangerous syscalls are blacklisted
+int enable_sandbox_basic(void);
+
+// enhanced filter
+// this mode allows only the necessary syscalls used during normal operation
+// extensive testing required !!!
+int enable_sandbox_full(void);
+#endif
+
+file_protected const char *file_getprogname(void);
+file_protected void file_setprogname(const char *);
+file_protected void file_err(int, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3), __noreturn__));
+file_protected void file_errx(int, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3), __noreturn__));
+file_protected void file_warn(const char *, ...)
+ __attribute__((__format__(__printf__, 1, 2)));
+file_protected void file_warnx(const char *, ...)
+ __attribute__((__format__(__printf__, 1, 2)));
+
+#if defined(HAVE_MMAP) && defined(HAVE_SYS_MMAN_H) && !defined(QUICK)
+#define QUICK
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#ifndef O_NONBLOCK
+#define O_NONBLOCK 0
+#endif
+
+#ifndef __cplusplus
+#if defined(__GNUC__) && (__GNUC__ >= 3)
+#define FILE_RCSID(id) \
+static const char rcsid[] __attribute__((__used__)) = id;
+#else
+#define FILE_RCSID(id) \
+static const char *rcsid(const char *p) { \
+ return rcsid(p = id); \
+}
+#endif
+#else
+#define FILE_RCSID(id)
+#endif
+#ifndef __RCSID
+#define __RCSID(a)
+#endif
+
+#endif /* __file_h__ */
diff --git a/src/file_opts.h b/src/file_opts.h
new file mode 100644
index 0000000..3aed7de
--- /dev/null
+++ b/src/file_opts.h
@@ -0,0 +1,89 @@
+/*
+ * Table of command-line options
+ *
+ * The first column specifies the short name, if any, or 0 if none.
+ * The second column specifies the long name.
+ * The third column specifies whether it takes a parameter.
+ * The fourth column specifies whether is marked as "default"
+ * if POSIXLY_CORRECT is defined: 1,
+ * if POSIXLY_CORRECT is not defined: 2.
+ * The fifth column is the documentation.
+ *
+ * N.B. The long options' order must correspond to the code in file.c,
+ * and OPTSTRING must be kept up-to-date with the short options.
+ * Pay particular attention to the numbers of long-only options in the
+ * switch statement!
+ */
+
+OPT_LONGONLY("help", 0, 0,
+ " display this help and exit\n", OPT_HELP)
+OPT('v', "version", 0, 0,
+ " output version information and exit\n")
+OPT('m', "magic-file", 1, 0,
+ " LIST use LIST as a colon-separated list of magic\n"
+ " number files\n")
+OPT('z', "uncompress", 0, 0,
+ " try to look inside compressed files\n")
+OPT('Z', "uncompress-noreport", 0, 0,
+ " only print the contents of compressed files\n")
+OPT('b', "brief", 0, 0,
+ " do not prepend filenames to output lines\n")
+OPT('c', "checking-printout", 0, 0,
+ " print the parsed form of the magic file, use in\n"
+ " conjunction with -m to debug a new magic file\n"
+ " before installing it\n")
+OPT('e', "exclude", 1, 0,
+ " TEST exclude TEST from the list of test to be\n"
+ " performed for file. Valid tests are:\n"
+ " %e\n")
+OPT_LONGONLY("exclude-quiet", 1, 0,
+ " TEST like exclude, but ignore unknown tests\n", OPT_EXCLUDE_QUIET)
+OPT('f', "files-from", 1, 0,
+ " FILE read the filenames to be examined from FILE\n")
+OPT('F', "separator", 1, 0,
+ " STRING use string as separator instead of `:'\n")
+OPT('i', "mime", 0, 0,
+ " output MIME type strings (--mime-type and\n"
+ " --mime-encoding)\n")
+OPT_LONGONLY("apple", 0, 0,
+ " output the Apple CREATOR/TYPE\n", OPT_APPLE)
+OPT_LONGONLY("extension", 0, 0,
+ " output a slash-separated list of extensions\n", OPT_EXTENSIONS)
+OPT_LONGONLY("mime-type", 0, 0,
+ " output the MIME type\n", OPT_MIME_TYPE)
+OPT_LONGONLY("mime-encoding", 0, 0,
+ " output the MIME encoding\n", OPT_MIME_ENCODING)
+OPT('k', "keep-going", 0, 0,
+ " don't stop at the first match\n")
+OPT('l', "list", 0, 0,
+ " list magic strength\n")
+#ifdef S_IFLNK
+OPT('L', "dereference", 0, 1,
+ " follow symlinks (default if POSIXLY_CORRECT is set)")
+OPT('h', "no-dereference", 0, 2,
+ " don't follow symlinks (default if POSIXLY_CORRECT is not set)")
+#endif
+OPT('n', "no-buffer", 0, 0,
+ " do not buffer output\n")
+OPT('N', "no-pad", 0, 0,
+ " do not pad output\n")
+OPT('0', "print0", 0, 0,
+ " terminate filenames with ASCII NUL\n")
+#if defined(HAVE_UTIME) || defined(HAVE_UTIMES)
+OPT('p', "preserve-date", 0, 0,
+ " preserve access times on files\n")
+#endif
+OPT('P', "parameter", 1, 0,
+ " set file engine parameter limits\n"
+ " %P\n")
+OPT('r', "raw", 0, 0,
+ " don't translate unprintable chars to \\ooo\n")
+OPT('s', "special-files", 0, 0,
+ " treat special (block/char devices) files as\n"
+ " ordinary ones\n")
+OPT('S', "no-sandbox", 0, 0,
+ " disable system call sandboxing\n")
+OPT('C', "compile", 0, 0,
+ " compile file specified by -m\n")
+OPT('d', "debug", 0, 0,
+ " print debugging messages\n")
diff --git a/src/fmtcheck.c b/src/fmtcheck.c
new file mode 100644
index 0000000..868d06f
--- /dev/null
+++ b/src/fmtcheck.c
@@ -0,0 +1,254 @@
+/* $NetBSD: fmtcheck.c,v 1.8 2008/04/28 20:22:59 martin Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code was contributed to The NetBSD Foundation by Allen Briggs.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: fmtcheck.c,v 1.6 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+enum __e_fmtcheck_types {
+ FMTCHECK_START,
+ FMTCHECK_SHORT,
+ FMTCHECK_INT,
+ FMTCHECK_LONG,
+ FMTCHECK_QUAD,
+ FMTCHECK_SHORTPOINTER,
+ FMTCHECK_INTPOINTER,
+ FMTCHECK_LONGPOINTER,
+ FMTCHECK_QUADPOINTER,
+ FMTCHECK_DOUBLE,
+ FMTCHECK_LONGDOUBLE,
+ FMTCHECK_STRING,
+ FMTCHECK_WIDTH,
+ FMTCHECK_PRECISION,
+ FMTCHECK_DONE,
+ FMTCHECK_UNKNOWN
+};
+typedef enum __e_fmtcheck_types EFT;
+
+#define RETURN(pf,f,r) do { \
+ *(pf) = (f); \
+ return r; \
+ } /*NOTREACHED*/ /*CONSTCOND*/ while (0)
+
+static EFT
+get_next_format_from_precision(const char **pf)
+{
+ int sh, lg, quad, longdouble;
+ const char *f;
+
+ sh = lg = quad = longdouble = 0;
+
+ f = *pf;
+ switch (*f) {
+ case 'h':
+ f++;
+ sh = 1;
+ break;
+ case 'l':
+ f++;
+ if (!*f) RETURN(pf,f,FMTCHECK_UNKNOWN);
+ if (*f == 'l') {
+ f++;
+ quad = 1;
+ } else {
+ lg = 1;
+ }
+ break;
+ case 'q':
+ f++;
+ quad = 1;
+ break;
+ case 'L':
+ f++;
+ longdouble = 1;
+ break;
+#ifdef WIN32
+ case 'I':
+ f++;
+ if (!*f) RETURN(pf,f,FMTCHECK_UNKNOWN);
+ if (*f == '3' && f[1] == '2') {
+ f += 2;
+ } else if (*f == '6' && f[1] == '4') {
+ f += 2;
+ quad = 1;
+ }
+#ifdef _WIN64
+ else {
+ quad = 1;
+ }
+#endif
+ break;
+#endif
+ default:
+ break;
+ }
+ if (!*f) RETURN(pf,f,FMTCHECK_UNKNOWN);
+ if (strchr("diouxX", *f)) {
+ if (longdouble)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ if (lg)
+ RETURN(pf,f,FMTCHECK_LONG);
+ if (quad)
+ RETURN(pf,f,FMTCHECK_QUAD);
+ RETURN(pf,f,FMTCHECK_INT);
+ }
+ if (*f == 'n') {
+ if (longdouble)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ if (sh)
+ RETURN(pf,f,FMTCHECK_SHORTPOINTER);
+ if (lg)
+ RETURN(pf,f,FMTCHECK_LONGPOINTER);
+ if (quad)
+ RETURN(pf,f,FMTCHECK_QUADPOINTER);
+ RETURN(pf,f,FMTCHECK_INTPOINTER);
+ }
+ if (strchr("DOU", *f)) {
+ if (sh + lg + quad + longdouble)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ RETURN(pf,f,FMTCHECK_LONG);
+ }
+ if (strchr("eEfg", *f)) {
+ if (longdouble)
+ RETURN(pf,f,FMTCHECK_LONGDOUBLE);
+ if (sh + lg + quad)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ RETURN(pf,f,FMTCHECK_DOUBLE);
+ }
+ if (*f == 'c') {
+ if (sh + lg + quad + longdouble)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ RETURN(pf,f,FMTCHECK_INT);
+ }
+ if (*f == 's') {
+ if (sh + lg + quad + longdouble)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ RETURN(pf,f,FMTCHECK_STRING);
+ }
+ if (*f == 'p') {
+ if (sh + lg + quad + longdouble)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ RETURN(pf,f,FMTCHECK_LONG);
+ }
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ /*NOTREACHED*/
+}
+
+static EFT
+get_next_format_from_width(const char **pf)
+{
+ const char *f;
+
+ f = *pf;
+ if (*f == '.') {
+ f++;
+ if (*f == '*') {
+ RETURN(pf,f,FMTCHECK_PRECISION);
+ }
+ /* eat any precision (empty is allowed) */
+ while (isdigit((unsigned char)*f)) f++;
+ if (!*f) RETURN(pf,f,FMTCHECK_UNKNOWN);
+ }
+ RETURN(pf,f,get_next_format_from_precision(pf));
+ /*NOTREACHED*/
+}
+
+static EFT
+get_next_format(const char **pf, EFT eft)
+{
+ int infmt;
+ const char *f;
+
+ if (eft == FMTCHECK_WIDTH) {
+ (*pf)++;
+ return get_next_format_from_width(pf);
+ } else if (eft == FMTCHECK_PRECISION) {
+ (*pf)++;
+ return get_next_format_from_precision(pf);
+ }
+
+ f = *pf;
+ infmt = 0;
+ while (!infmt) {
+ f = strchr(f, '%');
+ if (f == NULL)
+ RETURN(pf,f,FMTCHECK_DONE);
+ f++;
+ if (!*f)
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ if (*f != '%')
+ infmt = 1;
+ else
+ f++;
+ }
+
+ /* Eat any of the flags */
+ while (*f && (strchr("#0- +", *f)))
+ f++;
+
+ if (*f == '*') {
+ RETURN(pf,f,FMTCHECK_WIDTH);
+ }
+ /* eat any width */
+ while (isdigit((unsigned char)*f)) f++;
+ if (!*f) {
+ RETURN(pf,f,FMTCHECK_UNKNOWN);
+ }
+
+ RETURN(pf,f,get_next_format_from_width(pf));
+ /*NOTREACHED*/
+}
+
+const char *
+fmtcheck(const char *f1, const char *f2)
+{
+ const char *f1p, *f2p;
+ EFT f1t, f2t;
+
+ if (!f1) return f2;
+
+ f1p = f1;
+ f1t = FMTCHECK_START;
+ f2p = f2;
+ f2t = FMTCHECK_START;
+ while ((f1t = get_next_format(&f1p, f1t)) != FMTCHECK_DONE) {
+ if (f1t == FMTCHECK_UNKNOWN)
+ return f2;
+ f2t = get_next_format(&f2p, f2t);
+ if (f1t != f2t)
+ return f2;
+ }
+ return f1;
+}
diff --git a/src/fsmagic.c b/src/fsmagic.c
new file mode 100644
index 0000000..5a13dbd
--- /dev/null
+++ b/src/fsmagic.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * fsmagic - magic based on filesystem info - directory, special files, etc.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: fsmagic.c,v 1.85 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+/* Since major is a function on SVR4, we cannot use `ifndef major'. */
+#ifdef MAJOR_IN_MKDEV
+# include <sys/mkdev.h>
+# define HAVE_MAJOR
+#endif
+#ifdef HAVE_SYS_SYSMACROS_H
+# include <sys/sysmacros.h>
+#endif
+#ifdef MAJOR_IN_SYSMACROS
+# define HAVE_MAJOR
+#endif
+#if defined(major) && !defined(HAVE_MAJOR)
+/* Might be defined in sys/types.h. */
+# define HAVE_MAJOR
+#endif
+#ifdef WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+
+#ifndef HAVE_MAJOR
+# define major(dev) (((dev) >> 8) & 0xff)
+# define minor(dev) ((dev) & 0xff)
+#endif
+#undef HAVE_MAJOR
+#ifdef S_IFLNK
+file_private int
+bad_link(struct magic_set *ms, int err, char *buf)
+{
+ int mime = ms->flags & MAGIC_MIME;
+ if ((mime & MAGIC_MIME_TYPE) &&
+ file_printf(ms, "inode/symlink")
+ == -1)
+ return -1;
+ else if (!mime) {
+ if (ms->flags & MAGIC_ERROR) {
+ file_error(ms, err,
+ "broken symbolic link to %s", buf);
+ return -1;
+ }
+ if (file_printf(ms, "broken symbolic link to %s", buf) == -1)
+ return -1;
+ }
+ return 1;
+}
+#endif
+file_private int
+handle_mime(struct magic_set *ms, int mime, const char *str)
+{
+ if ((mime & MAGIC_MIME_TYPE)) {
+ if (file_printf(ms, "inode/%s", str) == -1)
+ return -1;
+ if ((mime & MAGIC_MIME_ENCODING) && file_printf(ms,
+ "; charset=") == -1)
+ return -1;
+ }
+ if ((mime & MAGIC_MIME_ENCODING) && file_printf(ms, "binary") == -1)
+ return -1;
+ return 0;
+}
+
+file_protected int
+file_fsmagic(struct magic_set *ms, const char *fn, struct stat *sb)
+{
+ int ret, did = 0;
+ int mime = ms->flags & MAGIC_MIME;
+ int silent = ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION);
+#ifdef S_IFLNK
+ char buf[BUFSIZ+4];
+ ssize_t nch;
+ struct stat tstatbuf;
+#endif
+
+ if (fn == NULL)
+ return 0;
+
+#define COMMA (did++ ? ", " : "")
+ /*
+ * Fstat is cheaper but fails for files you don't have read perms on.
+ * On 4.2BSD and similar systems, use lstat() to identify symlinks.
+ */
+#ifdef S_IFLNK
+ if ((ms->flags & MAGIC_SYMLINK) == 0)
+ ret = lstat(fn, sb);
+ else
+#endif
+ ret = stat(fn, sb); /* don't merge into if; see "ret =" above */
+
+#ifdef WIN32
+ {
+ HANDLE hFile = CreateFile((LPCSTR)fn, 0, FILE_SHARE_DELETE |
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0,
+ NULL);
+ if (hFile != INVALID_HANDLE_VALUE) {
+ /*
+ * Stat failed, but we can still open it - assume it's
+ * a block device, if nothing else.
+ */
+ if (ret) {
+ sb->st_mode = S_IFBLK;
+ ret = 0;
+ }
+ switch (GetFileType(hFile)) {
+ case FILE_TYPE_CHAR:
+ sb->st_mode |= S_IFCHR;
+ sb->st_mode &= ~S_IFREG;
+ break;
+ case FILE_TYPE_PIPE:
+ sb->st_mode |= S_IFIFO;
+ sb->st_mode &= ~S_IFREG;
+ break;
+ }
+ CloseHandle(hFile);
+ }
+ }
+#endif
+
+ if (ret) {
+ if (ms->flags & MAGIC_ERROR) {
+ file_error(ms, errno, "cannot stat `%s'", fn);
+ return -1;
+ }
+ if (file_printf(ms, "cannot open `%s' (%s)",
+ fn, strerror(errno)) == -1)
+ return -1;
+ return 0;
+ }
+
+ ret = 1;
+ if (!mime && !silent) {
+#ifdef S_ISUID
+ if (sb->st_mode & S_ISUID)
+ if (file_printf(ms, "%ssetuid", COMMA) == -1)
+ return -1;
+#endif
+#ifdef S_ISGID
+ if (sb->st_mode & S_ISGID)
+ if (file_printf(ms, "%ssetgid", COMMA) == -1)
+ return -1;
+#endif
+#ifdef S_ISVTX
+ if (sb->st_mode & S_ISVTX)
+ if (file_printf(ms, "%ssticky", COMMA) == -1)
+ return -1;
+#endif
+ }
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFDIR:
+ if (mime) {
+ if (handle_mime(ms, mime, "directory") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms, "%sdirectory", COMMA) == -1)
+ return -1;
+ break;
+#ifdef S_IFCHR
+ case S_IFCHR:
+ /*
+ * If -s has been specified, treat character special files
+ * like ordinary files. Otherwise, just report that they
+ * are block special files and go on to the next file.
+ */
+ if ((ms->flags & MAGIC_DEVICES) != 0) {
+ ret = 0;
+ break;
+ }
+ if (mime) {
+ if (handle_mime(ms, mime, "chardevice") == -1)
+ return -1;
+ } else if (silent) {
+ } else {
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+# ifdef dv_unit
+ if (file_printf(ms, "%scharacter special (%d/%d/%d)",
+ COMMA, major(sb->st_rdev), dv_unit(sb->st_rdev),
+ dv_subunit(sb->st_rdev)) == -1)
+ return -1;
+# else
+ if (file_printf(ms, "%scharacter special (%ld/%ld)",
+ COMMA, (long)major(sb->st_rdev),
+ (long)minor(sb->st_rdev)) == -1)
+ return -1;
+# endif
+#else
+ if (file_printf(ms, "%scharacter special", COMMA) == -1)
+ return -1;
+#endif
+ }
+ break;
+#endif
+#ifdef S_IFBLK
+ case S_IFBLK:
+ /*
+ * If -s has been specified, treat block special files
+ * like ordinary files. Otherwise, just report that they
+ * are block special files and go on to the next file.
+ */
+ if ((ms->flags & MAGIC_DEVICES) != 0) {
+ ret = 0;
+ break;
+ }
+ if (mime) {
+ if (handle_mime(ms, mime, "blockdevice") == -1)
+ return -1;
+ } else if (silent) {
+ } else {
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+# ifdef dv_unit
+ if (file_printf(ms, "%sblock special (%d/%d/%d)",
+ COMMA, major(sb->st_rdev), dv_unit(sb->st_rdev),
+ dv_subunit(sb->st_rdev)) == -1)
+ return -1;
+# else
+ if (file_printf(ms, "%sblock special (%ld/%ld)",
+ COMMA, (long)major(sb->st_rdev),
+ (long)minor(sb->st_rdev)) == -1)
+ return -1;
+# endif
+#else
+ if (file_printf(ms, "%sblock special", COMMA) == -1)
+ return -1;
+#endif
+ }
+ break;
+#endif
+ /* TODO add code to handle V7 MUX and Blit MUX files */
+#ifdef S_IFIFO
+ case S_IFIFO:
+ if((ms->flags & MAGIC_DEVICES) != 0)
+ break;
+ if (mime) {
+ if (handle_mime(ms, mime, "fifo") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms, "%sfifo (named pipe)", COMMA) == -1)
+ return -1;
+ break;
+#endif
+#ifdef S_IFDOOR
+ case S_IFDOOR:
+ if (mime) {
+ if (handle_mime(ms, mime, "door") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms, "%sdoor", COMMA) == -1)
+ return -1;
+ break;
+#endif
+#ifdef S_IFLNK
+ case S_IFLNK:
+ if ((nch = readlink(fn, buf, BUFSIZ-1)) <= 0) {
+ if (ms->flags & MAGIC_ERROR) {
+ file_error(ms, errno, "unreadable symlink `%s'",
+ fn);
+ return -1;
+ }
+ if (mime) {
+ if (handle_mime(ms, mime, "symlink") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms,
+ "%sunreadable symlink `%s' (%s)", COMMA, fn,
+ strerror(errno)) == -1)
+ return -1;
+ break;
+ }
+ buf[nch] = '\0'; /* readlink(2) does not do this */
+
+ /* If broken symlink, say so and quit early. */
+#ifdef __linux__
+ /*
+ * linux procfs/devfs makes symlinks like pipe:[3515864880]
+ * that we can't stat their readlink output, so stat the
+ * original filename instead.
+ */
+ if (stat(fn, &tstatbuf) < 0)
+ return bad_link(ms, errno, buf);
+#else
+ if (*buf == '/') {
+ if (stat(buf, &tstatbuf) < 0)
+ return bad_link(ms, errno, buf);
+ } else {
+ char *tmp;
+ char buf2[BUFSIZ+BUFSIZ+4];
+
+ if ((tmp = CCAST(char *, strrchr(fn, '/'))) == NULL) {
+ tmp = buf; /* in current directory anyway */
+ } else {
+ if (tmp - fn + 1 > BUFSIZ) {
+ if (ms->flags & MAGIC_ERROR) {
+ file_error(ms, 0,
+ "path too long: `%s'", buf);
+ return -1;
+ }
+ if (mime) {
+ if (handle_mime(ms, mime,
+ "x-path-too-long") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms,
+ "%spath too long: `%s'", COMMA,
+ fn) == -1)
+ return -1;
+ break;
+ }
+ /* take dir part */
+ (void)strlcpy(buf2, fn, sizeof buf2);
+ buf2[tmp - fn + 1] = '\0';
+ /* plus (rel) link */
+ (void)strlcat(buf2, buf, sizeof buf2);
+ tmp = buf2;
+ }
+ if (stat(tmp, &tstatbuf) < 0)
+ return bad_link(ms, errno, buf);
+ }
+#endif
+
+ /* Otherwise, handle it. */
+ if ((ms->flags & MAGIC_SYMLINK) != 0) {
+ const char *p;
+ ms->flags &= MAGIC_SYMLINK;
+ p = magic_file(ms, buf);
+ ms->flags |= MAGIC_SYMLINK;
+ if (p == NULL)
+ return -1;
+ } else { /* just print what it points to */
+ if (mime) {
+ if (handle_mime(ms, mime, "symlink") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms, "%ssymbolic link to %s",
+ COMMA, buf) == -1)
+ return -1;
+ }
+ break;
+#endif
+#ifdef S_IFSOCK
+#ifndef __COHERENT__
+ case S_IFSOCK:
+ if (mime) {
+ if (handle_mime(ms, mime, "socket") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms, "%ssocket", COMMA) == -1)
+ return -1;
+ break;
+#endif
+#endif
+ case S_IFREG:
+ /*
+ * regular file, check next possibility
+ *
+ * If stat() tells us the file has zero length, report here that
+ * the file is empty, so we can skip all the work of opening and
+ * reading the file.
+ * But if the -s option has been given, we skip this
+ * optimization, since on some systems, stat() reports zero
+ * size for raw disk partitions. (If the block special device
+ * really has zero length, the fact that it is empty will be
+ * detected and reported correctly when we read the file.)
+ */
+ if ((ms->flags & MAGIC_DEVICES) == 0 && sb->st_size == 0) {
+ if (mime) {
+ if (handle_mime(ms, mime, "x-empty") == -1)
+ return -1;
+ } else if (silent) {
+ } else if (file_printf(ms, "%sempty", COMMA) == -1)
+ return -1;
+ break;
+ }
+ ret = 0;
+ break;
+
+ default:
+ file_error(ms, 0, "invalid mode 0%o", sb->st_mode);
+ return -1;
+ /*NOTREACHED*/
+ }
+
+ if (!silent && !mime && did && ret == 0) {
+ if (file_printf(ms, " ") == -1)
+ return -1;
+ }
+ /*
+ * If we were looking for extensions or apple (silent) it is not our
+ * job to print here, so don't count this as a match.
+ */
+ if (ret == 1 && silent)
+ return 0;
+ return ret;
+}
diff --git a/src/funcs.c b/src/funcs.c
new file mode 100644
index 0000000..d4fefcd
--- /dev/null
+++ b/src/funcs.c
@@ -0,0 +1,918 @@
+/*
+ * Copyright (c) Christos Zoulas 2003.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: funcs.c,v 1.136 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <assert.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* for pipe2() */
+#endif
+#if defined(HAVE_WCHAR_H)
+#include <wchar.h>
+#endif
+#if defined(HAVE_WCTYPE_H)
+#include <wctype.h>
+#endif
+#include <limits.h>
+
+#ifndef SIZE_MAX
+#define SIZE_MAX ((size_t)~0)
+#endif
+
+file_protected char *
+file_copystr(char *buf, size_t blen, size_t width, const char *str)
+{
+ if (blen == 0)
+ return buf;
+ if (width >= blen)
+ width = blen - 1;
+ memcpy(buf, str, width);
+ buf[width] = '\0';
+ return buf;
+}
+
+file_private void
+file_clearbuf(struct magic_set *ms)
+{
+ free(ms->o.buf);
+ ms->o.buf = NULL;
+ ms->o.blen = 0;
+}
+
+file_private int
+file_checkfield(char *msg, size_t mlen, const char *what, const char **pp)
+{
+ const char *p = *pp;
+ int fw = 0;
+
+ while (*p && isdigit((unsigned char)*p))
+ fw = fw * 10 + (*p++ - '0');
+
+ *pp = p;
+
+ if (fw < 1024)
+ return 1;
+ if (msg)
+ snprintf(msg, mlen, "field %s too large: %d", what, fw);
+
+ return 0;
+}
+
+file_protected int
+file_checkfmt(char *msg, size_t mlen, const char *fmt)
+{
+ const char *p;
+ for (p = fmt; *p; p++) {
+ if (*p != '%')
+ continue;
+ if (*++p == '%')
+ continue;
+ // Skip uninteresting.
+ while (strchr("#0.'+- ", *p) != NULL)
+ p++;
+ if (*p == '*') {
+ if (msg)
+ snprintf(msg, mlen, "* not allowed in format");
+ return -1;
+ }
+
+ if (!file_checkfield(msg, mlen, "width", &p))
+ return -1;
+
+ if (*p == '.') {
+ p++;
+ if (!file_checkfield(msg, mlen, "precision", &p))
+ return -1;
+ }
+
+ if (!isalpha((unsigned char)*p)) {
+ if (msg)
+ snprintf(msg, mlen, "bad format char: %c", *p);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Like printf, only we append to a buffer.
+ */
+file_protected int
+file_vprintf(struct magic_set *ms, const char *fmt, va_list ap)
+{
+ int len;
+ char *buf, *newstr;
+ char tbuf[1024];
+
+ if (ms->event_flags & EVENT_HAD_ERR)
+ return 0;
+
+ if (file_checkfmt(tbuf, sizeof(tbuf), fmt)) {
+ file_clearbuf(ms);
+ file_error(ms, 0, "Bad magic format `%s' (%s)", fmt, tbuf);
+ return -1;
+ }
+
+ len = vasprintf(&buf, fmt, ap);
+ if (len < 0 || (size_t)len > 1024 || len + ms->o.blen > 1024 * 1024) {
+ size_t blen = ms->o.blen;
+ free(buf);
+ file_clearbuf(ms);
+ file_error(ms, 0, "Output buffer space exceeded %d+%"
+ SIZE_T_FORMAT "u", len, blen);
+ return -1;
+ }
+
+ if (ms->o.buf != NULL) {
+ len = asprintf(&newstr, "%s%s", ms->o.buf, buf);
+ free(buf);
+ if (len < 0)
+ goto out;
+ free(ms->o.buf);
+ buf = newstr;
+ }
+ ms->o.buf = buf;
+ ms->o.blen = len;
+ return 0;
+out:
+ file_clearbuf(ms);
+ file_error(ms, errno, "vasprintf failed");
+ return -1;
+}
+
+file_protected int
+file_printf(struct magic_set *ms, const char *fmt, ...)
+{
+ int rv;
+ va_list ap;
+
+ va_start(ap, fmt);
+ rv = file_vprintf(ms, fmt, ap);
+ va_end(ap);
+ return rv;
+}
+
+/*
+ * error - print best error message possible
+ */
+/*VARARGS*/
+__attribute__((__format__(__printf__, 3, 0)))
+file_private void
+file_error_core(struct magic_set *ms, int error, const char *f, va_list va,
+ size_t lineno)
+{
+ /* Only the first error is ok */
+ if (ms->event_flags & EVENT_HAD_ERR)
+ return;
+ if (lineno != 0) {
+ file_clearbuf(ms);
+ (void)file_printf(ms, "line %" SIZE_T_FORMAT "u:", lineno);
+ }
+ if (ms->o.buf && *ms->o.buf)
+ (void)file_printf(ms, " ");
+ (void)file_vprintf(ms, f, va);
+ if (error > 0)
+ (void)file_printf(ms, " (%s)", strerror(error));
+ ms->event_flags |= EVENT_HAD_ERR;
+ ms->error = error;
+}
+
+/*VARARGS*/
+file_protected void
+file_error(struct magic_set *ms, int error, const char *f, ...)
+{
+ va_list va;
+ va_start(va, f);
+ file_error_core(ms, error, f, va, 0);
+ va_end(va);
+}
+
+/*
+ * Print an error with magic line number.
+ */
+/*VARARGS*/
+file_protected void
+file_magerror(struct magic_set *ms, const char *f, ...)
+{
+ va_list va;
+ va_start(va, f);
+ file_error_core(ms, 0, f, va, ms->line);
+ va_end(va);
+}
+
+file_protected void
+file_oomem(struct magic_set *ms, size_t len)
+{
+ file_error(ms, errno, "cannot allocate %" SIZE_T_FORMAT "u bytes",
+ len);
+}
+
+file_protected void
+file_badseek(struct magic_set *ms)
+{
+ file_error(ms, errno, "error seeking");
+}
+
+file_protected void
+file_badread(struct magic_set *ms)
+{
+ file_error(ms, errno, "error reading");
+}
+
+#ifndef COMPILE_ONLY
+#define FILE_SEPARATOR "\n- "
+
+file_protected int
+file_separator(struct magic_set *ms)
+{
+ return file_printf(ms, FILE_SEPARATOR);
+}
+
+static void
+trim_separator(struct magic_set *ms)
+{
+ size_t l;
+
+ if (ms->o.buf == NULL)
+ return;
+
+ l = strlen(ms->o.buf);
+ if (l < sizeof(FILE_SEPARATOR))
+ return;
+
+ l -= sizeof(FILE_SEPARATOR) - 1;
+ if (strcmp(ms->o.buf + l, FILE_SEPARATOR) != 0)
+ return;
+
+ ms->o.buf[l] = '\0';
+}
+
+static int
+checkdone(struct magic_set *ms, int *rv)
+{
+ if ((ms->flags & MAGIC_CONTINUE) == 0)
+ return 1;
+ if (file_separator(ms) == -1)
+ *rv = -1;
+ return 0;
+}
+
+file_protected int
+file_default(struct magic_set *ms, size_t nb)
+{
+ if (ms->flags & MAGIC_MIME) {
+ if ((ms->flags & MAGIC_MIME_TYPE) &&
+ file_printf(ms, "application/%s",
+ nb ? "octet-stream" : "x-empty") == -1)
+ return -1;
+ return 1;
+ }
+ if (ms->flags & MAGIC_APPLE) {
+ if (file_printf(ms, "UNKNUNKN") == -1)
+ return -1;
+ return 1;
+ }
+ if (ms->flags & MAGIC_EXTENSION) {
+ if (file_printf(ms, "???") == -1)
+ return -1;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * The magic detection functions return:
+ * 1: found
+ * 0: not found
+ * -1: error
+ */
+/*ARGSUSED*/
+file_protected int
+file_buffer(struct magic_set *ms, int fd, struct stat *st,
+ const char *inname __attribute__ ((__unused__)),
+ const void *buf, size_t nb)
+{
+ int m = 0, rv = 0, looks_text = 0;
+ const char *code = NULL;
+ const char *code_mime = "binary";
+ const char *def = "data";
+ const char *ftype = NULL;
+ char *rbuf = NULL;
+ struct buffer b;
+
+ buffer_init(&b, fd, st, buf, nb);
+ ms->mode = b.st.st_mode;
+
+ if (nb == 0) {
+ def = "empty";
+ goto simple;
+ } else if (nb == 1) {
+ def = "very short file (no magic)";
+ goto simple;
+ }
+
+ if ((ms->flags & MAGIC_NO_CHECK_ENCODING) == 0) {
+ looks_text = file_encoding(ms, &b, NULL, 0,
+ &code, &code_mime, &ftype);
+ }
+
+#ifdef __EMX__
+ if ((ms->flags & MAGIC_NO_CHECK_APPTYPE) == 0 && inname) {
+ m = file_os2_apptype(ms, inname, &b);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try os2_apptype %d]\n", m);
+ switch (m) {
+ case -1:
+ return -1;
+ case 0:
+ break;
+ default:
+ return 1;
+ }
+ }
+#endif
+#if HAVE_FORK
+ /* try compression stuff */
+ if ((ms->flags & MAGIC_NO_CHECK_COMPRESS) == 0) {
+ m = file_zmagic(ms, &b, inname);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try zmagic %d]\n", m);
+ if (m) {
+ goto done_encoding;
+ }
+ }
+#endif
+ /* Check if we have a tar file */
+ if ((ms->flags & MAGIC_NO_CHECK_TAR) == 0) {
+ m = file_is_tar(ms, &b);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try tar %d]\n", m);
+ if (m) {
+ if (checkdone(ms, &rv))
+ goto done;
+ }
+ }
+
+ /* Check if we have a JSON file */
+ if ((ms->flags & MAGIC_NO_CHECK_JSON) == 0) {
+ m = file_is_json(ms, &b);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try json %d]\n", m);
+ if (m) {
+ if (checkdone(ms, &rv))
+ goto done;
+ }
+ }
+
+ /* Check if we have a CSV file */
+ if ((ms->flags & MAGIC_NO_CHECK_CSV) == 0) {
+ m = file_is_csv(ms, &b, looks_text);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try csv %d]\n", m);
+ if (m) {
+ if (checkdone(ms, &rv))
+ goto done;
+ }
+ }
+
+ /* Check if we have a CDF file */
+ if ((ms->flags & MAGIC_NO_CHECK_CDF) == 0) {
+ m = file_trycdf(ms, &b);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try cdf %d]\n", m);
+ if (m) {
+ if (checkdone(ms, &rv))
+ goto done;
+ }
+ }
+#ifdef BUILTIN_ELF
+ if ((ms->flags & MAGIC_NO_CHECK_ELF) == 0 && nb > 5 && fd != -1) {
+ file_pushbuf_t *pb;
+ /*
+ * We matched something in the file, so this
+ * *might* be an ELF file, and the file is at
+ * least 5 bytes long, so if it's an ELF file
+ * it has at least one byte past the ELF magic
+ * number - try extracting information from the
+ * ELF headers that cannot easily be extracted
+ * with rules in the magic file. We we don't
+ * print the information yet.
+ */
+ if ((pb = file_push_buffer(ms)) == NULL)
+ return -1;
+
+ rv = file_tryelf(ms, &b);
+ rbuf = file_pop_buffer(ms, pb);
+ if (rv == -1) {
+ free(rbuf);
+ rbuf = NULL;
+ }
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try elf %d]\n", m);
+ }
+#endif
+
+ /* try soft magic tests */
+ if ((ms->flags & MAGIC_NO_CHECK_SOFT) == 0) {
+ m = file_softmagic(ms, &b, NULL, NULL, BINTEST, looks_text);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try softmagic %d]\n", m);
+ if (m == 1 && rbuf) {
+ if (file_printf(ms, "%s", rbuf) == -1)
+ goto done;
+ }
+ if (m) {
+ if (checkdone(ms, &rv))
+ goto done;
+ }
+ }
+
+ /* try text properties */
+ if ((ms->flags & MAGIC_NO_CHECK_TEXT) == 0) {
+
+ m = file_ascmagic(ms, &b, looks_text);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void)fprintf(stderr, "[try ascmagic %d]\n", m);
+ if (m) {
+ goto done;
+ }
+ }
+
+simple:
+ /* give up */
+ if (m == 0) {
+ m = 1;
+ rv = file_default(ms, nb);
+ if (rv == 0)
+ if (file_printf(ms, "%s", def) == -1)
+ rv = -1;
+ }
+ done:
+ trim_separator(ms);
+ if ((ms->flags & MAGIC_MIME_ENCODING) != 0) {
+ if (ms->flags & MAGIC_MIME_TYPE)
+ if (file_printf(ms, "; charset=") == -1)
+ rv = -1;
+ if (file_printf(ms, "%s", code_mime) == -1)
+ rv = -1;
+ }
+#if HAVE_FORK
+ done_encoding:
+#endif
+ free(rbuf);
+ buffer_fini(&b);
+ if (rv)
+ return rv;
+
+ return m;
+}
+#endif
+
+file_protected int
+file_reset(struct magic_set *ms, int checkloaded)
+{
+ if (checkloaded && ms->mlist[0] == NULL) {
+ file_error(ms, 0, "no magic files loaded");
+ return -1;
+ }
+ file_clearbuf(ms);
+ if (ms->o.pbuf) {
+ free(ms->o.pbuf);
+ ms->o.pbuf = NULL;
+ }
+ ms->event_flags &= ~EVENT_HAD_ERR;
+ ms->error = -1;
+ return 0;
+}
+
+#define OCTALIFY(n, o) \
+ /*LINTED*/ \
+ (void)(*(n)++ = '\\', \
+ *(n)++ = ((CAST(uint32_t, *(o)) >> 6) & 3) + '0', \
+ *(n)++ = ((CAST(uint32_t, *(o)) >> 3) & 7) + '0', \
+ *(n)++ = ((CAST(uint32_t, *(o)) >> 0) & 7) + '0', \
+ (o)++)
+
+file_protected const char *
+file_getbuffer(struct magic_set *ms)
+{
+ char *pbuf, *op, *np;
+ size_t psize, len;
+
+ if (ms->event_flags & EVENT_HAD_ERR)
+ return NULL;
+
+ if (ms->flags & MAGIC_RAW)
+ return ms->o.buf;
+
+ if (ms->o.buf == NULL)
+ return NULL;
+
+ /* * 4 is for octal representation, + 1 is for NUL */
+ len = strlen(ms->o.buf);
+ if (len > (SIZE_MAX - 1) / 4) {
+ file_oomem(ms, len);
+ return NULL;
+ }
+ psize = len * 4 + 1;
+ if ((pbuf = CAST(char *, realloc(ms->o.pbuf, psize))) == NULL) {
+ file_oomem(ms, psize);
+ return NULL;
+ }
+ ms->o.pbuf = pbuf;
+
+#if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
+ {
+ mbstate_t state;
+ wchar_t nextchar;
+ int mb_conv = 1;
+ size_t bytesconsumed;
+ char *eop;
+ (void)memset(&state, 0, sizeof(mbstate_t));
+
+ np = ms->o.pbuf;
+ op = ms->o.buf;
+ eop = op + len;
+
+ while (op < eop) {
+ bytesconsumed = mbrtowc(&nextchar, op,
+ CAST(size_t, eop - op), &state);
+ if (bytesconsumed == CAST(size_t, -1) ||
+ bytesconsumed == CAST(size_t, -2)) {
+ mb_conv = 0;
+ break;
+ }
+
+ if (iswprint(nextchar)) {
+ (void)memcpy(np, op, bytesconsumed);
+ op += bytesconsumed;
+ np += bytesconsumed;
+ } else {
+ while (bytesconsumed-- > 0)
+ OCTALIFY(np, op);
+ }
+ }
+ *np = '\0';
+
+ /* Parsing succeeded as a multi-byte sequence */
+ if (mb_conv != 0)
+ return ms->o.pbuf;
+ }
+#endif
+
+ for (np = ms->o.pbuf, op = ms->o.buf; *op;) {
+ if (isprint(CAST(unsigned char, *op))) {
+ *np++ = *op++;
+ } else {
+ OCTALIFY(np, op);
+ }
+ }
+ *np = '\0';
+ return ms->o.pbuf;
+}
+
+file_protected int
+file_check_mem(struct magic_set *ms, unsigned int level)
+{
+ size_t len;
+
+ if (level >= ms->c.len) {
+ len = (ms->c.len = 20 + level) * sizeof(*ms->c.li);
+ ms->c.li = CAST(struct level_info *, (ms->c.li == NULL) ?
+ malloc(len) :
+ realloc(ms->c.li, len));
+ if (ms->c.li == NULL) {
+ file_oomem(ms, len);
+ return -1;
+ }
+ }
+ ms->c.li[level].got_match = 0;
+#ifdef ENABLE_CONDITIONALS
+ ms->c.li[level].last_match = 0;
+ ms->c.li[level].last_cond = COND_NONE;
+#endif /* ENABLE_CONDITIONALS */
+ return 0;
+}
+
+file_protected size_t
+file_printedlen(const struct magic_set *ms)
+{
+ return ms->o.blen;
+}
+
+file_protected int
+file_replace(struct magic_set *ms, const char *pat, const char *rep)
+{
+ file_regex_t rx;
+ int rc, rv = -1;
+
+ rc = file_regcomp(ms, &rx, pat, REG_EXTENDED);
+ if (rc == 0) {
+ regmatch_t rm;
+ int nm = 0;
+ while (file_regexec(ms, &rx, ms->o.buf, 1, &rm, 0) == 0) {
+ ms->o.buf[rm.rm_so] = '\0';
+ if (file_printf(ms, "%s%s", rep,
+ rm.rm_eo != 0 ? ms->o.buf + rm.rm_eo : "") == -1)
+ goto out;
+ nm++;
+ }
+ rv = nm;
+ }
+out:
+ file_regfree(&rx);
+ return rv;
+}
+
+file_private int
+check_regex(struct magic_set *ms, const char *pat)
+{
+ char sbuf[512];
+ unsigned char oc = '\0';
+
+ for (const char *p = pat; *p; p++) {
+ unsigned char c = *p;
+ // Avoid repetition
+ if (c == oc && strchr("?*+{", c) != NULL) {
+ size_t len = strlen(pat);
+ file_magwarn(ms,
+ "repetition-operator operand `%c' "
+ "invalid in regex `%s'", c,
+ file_printable(ms, sbuf, sizeof(sbuf), pat, len));
+ return -1;
+ }
+ oc = c;
+ if (isprint(c) || isspace(c) || c == '\b'
+ || c == 0x8a) // XXX: apple magic fixme
+ continue;
+ size_t len = strlen(pat);
+ file_magwarn(ms,
+ "non-ascii characters in regex \\%#o `%s'",
+ c, file_printable(ms, sbuf, sizeof(sbuf), pat, len));
+ return -1;
+ }
+ return 0;
+}
+
+file_protected int
+file_regcomp(struct magic_set *ms file_locale_used, file_regex_t *rx,
+ const char *pat, int flags)
+{
+ if (check_regex(ms, pat) == -1)
+ return -1;
+
+#ifdef USE_C_LOCALE
+ locale_t old = uselocale(ms->c_lc_ctype);
+ assert(old != NULL);
+#else
+ char old[1024];
+ strlcpy(old, setlocale(LC_CTYPE, NULL), sizeof(old));
+ (void)setlocale(LC_CTYPE, "C");
+#endif
+ int rc;
+ rc = regcomp(rx, pat, flags);
+
+#ifdef USE_C_LOCALE
+ uselocale(old);
+#else
+ (void)setlocale(LC_CTYPE, old);
+#endif
+ if (rc > 0 && (ms->flags & MAGIC_CHECK)) {
+ char errmsg[512], buf[512];
+
+ (void)regerror(rc, rx, errmsg, sizeof(errmsg));
+ file_magerror(ms, "regex error %d for `%s', (%s)", rc,
+ file_printable(ms, buf, sizeof(buf), pat, strlen(pat)),
+ errmsg);
+ }
+ return rc;
+}
+
+/*ARGSUSED*/
+file_protected int
+file_regexec(struct magic_set *ms file_locale_used, file_regex_t *rx,
+ const char *str, size_t nmatch, regmatch_t* pmatch, int eflags)
+{
+#ifdef USE_C_LOCALE
+ locale_t old = uselocale(ms->c_lc_ctype);
+ assert(old != NULL);
+#else
+ char old[1024];
+ strlcpy(old, setlocale(LC_CTYPE, NULL), sizeof(old));
+ (void)setlocale(LC_CTYPE, "C");
+#endif
+ int rc;
+ /* XXX: force initialization because glibc does not always do this */
+ if (nmatch != 0)
+ memset(pmatch, 0, nmatch * sizeof(*pmatch));
+ rc = regexec(rx, str, nmatch, pmatch, eflags);
+#ifdef USE_C_LOCALE
+ uselocale(old);
+#else
+ (void)setlocale(LC_CTYPE, old);
+#endif
+ return rc;
+}
+
+file_protected void
+file_regfree(file_regex_t *rx)
+{
+ regfree(rx);
+}
+
+file_protected file_pushbuf_t *
+file_push_buffer(struct magic_set *ms)
+{
+ file_pushbuf_t *pb;
+
+ if (ms->event_flags & EVENT_HAD_ERR)
+ return NULL;
+
+ if ((pb = (CAST(file_pushbuf_t *, malloc(sizeof(*pb))))) == NULL)
+ return NULL;
+
+ pb->buf = ms->o.buf;
+ pb->blen = ms->o.blen;
+ pb->offset = ms->offset;
+
+ ms->o.buf = NULL;
+ ms->o.blen = 0;
+ ms->offset = 0;
+
+ return pb;
+}
+
+file_protected char *
+file_pop_buffer(struct magic_set *ms, file_pushbuf_t *pb)
+{
+ char *rbuf;
+
+ if (ms->event_flags & EVENT_HAD_ERR) {
+ free(pb->buf);
+ free(pb);
+ return NULL;
+ }
+
+ rbuf = ms->o.buf;
+
+ ms->o.buf = pb->buf;
+ ms->o.blen = pb->blen;
+ ms->offset = pb->offset;
+
+ free(pb);
+ return rbuf;
+}
+
+/*
+ * convert string to ascii printable format.
+ */
+file_protected char *
+file_printable(struct magic_set *ms, char *buf, size_t bufsiz,
+ const char *str, size_t slen)
+{
+ char *ptr, *eptr = buf + bufsiz - 1;
+ const unsigned char *s = RCAST(const unsigned char *, str);
+ const unsigned char *es = s + slen;
+
+ for (ptr = buf; ptr < eptr && s < es && *s; s++) {
+ if ((ms->flags & MAGIC_RAW) != 0 || isprint(*s)) {
+ *ptr++ = *s;
+ continue;
+ }
+ if (ptr >= eptr - 3)
+ break;
+ *ptr++ = '\\';
+ *ptr++ = ((CAST(unsigned int, *s) >> 6) & 7) + '0';
+ *ptr++ = ((CAST(unsigned int, *s) >> 3) & 7) + '0';
+ *ptr++ = ((CAST(unsigned int, *s) >> 0) & 7) + '0';
+ }
+ *ptr = '\0';
+ return buf;
+}
+
+struct guid {
+ uint32_t data1;
+ uint16_t data2;
+ uint16_t data3;
+ uint8_t data4[8];
+};
+
+file_protected int
+file_parse_guid(const char *s, uint64_t *guid)
+{
+ struct guid *g = CAST(struct guid *, CAST(void *, guid));
+#ifndef WIN32
+ return sscanf(s,
+ "%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
+ &g->data1, &g->data2, &g->data3, &g->data4[0], &g->data4[1],
+ &g->data4[2], &g->data4[3], &g->data4[4], &g->data4[5],
+ &g->data4[6], &g->data4[7]) == 11 ? 0 : -1;
+#else
+ /* MS-Windows runtime doesn't support %hhx, except under
+ non-default __USE_MINGW_ANSI_STDIO. */
+ uint16_t data16[8];
+ int rv = sscanf(s, "%8x-%4hx-%4hx-%2hx%2hx-%2hx%2hx%2hx%2hx%2hx%2hx",
+ &g->data1, &g->data2, &g->data3, &data16[0], &data16[1],
+ &data16[2], &data16[3], &data16[4], &data16[5],
+ &data16[6], &data16[7]) == 11 ? 0 : -1;
+ int i;
+ for (i = 0; i < 8; i++)
+ g->data4[i] = data16[i];
+ return rv;
+#endif
+}
+
+file_protected int
+file_print_guid(char *str, size_t len, const uint64_t *guid)
+{
+ const struct guid *g = CAST(const struct guid *,
+ CAST(const void *, guid));
+
+#ifndef WIN32
+ return snprintf(str, len, "%.8X-%.4hX-%.4hX-%.2hhX%.2hhX-"
+ "%.2hhX%.2hhX%.2hhX%.2hhX%.2hhX%.2hhX",
+ g->data1, g->data2, g->data3, g->data4[0], g->data4[1],
+ g->data4[2], g->data4[3], g->data4[4], g->data4[5],
+ g->data4[6], g->data4[7]);
+#else
+ return snprintf(str, len, "%.8X-%.4hX-%.4hX-%.2hX%.2hX-"
+ "%.2hX%.2hX%.2hX%.2hX%.2hX%.2hX",
+ g->data1, g->data2, g->data3, g->data4[0], g->data4[1],
+ g->data4[2], g->data4[3], g->data4[4], g->data4[5],
+ g->data4[6], g->data4[7]);
+#endif
+}
+
+file_protected int
+file_pipe_closexec(int *fds)
+{
+#ifdef HAVE_PIPE2
+ return pipe2(fds, O_CLOEXEC);
+#else
+ if (pipe(fds) == -1)
+ return -1;
+# ifdef F_SETFD
+ (void)fcntl(fds[0], F_SETFD, FD_CLOEXEC);
+ (void)fcntl(fds[1], F_SETFD, FD_CLOEXEC);
+# endif
+ return 0;
+#endif
+}
+
+file_protected int
+file_clear_closexec(int fd) {
+#ifdef F_SETFD
+ return fcntl(fd, F_SETFD, 0);
+#else
+ return 0;
+#endif
+}
+
+file_protected char *
+file_strtrim(char *str)
+{
+ char *last;
+
+ while (isspace(CAST(unsigned char, *str)))
+ str++;
+ last = str;
+ while (*last)
+ last++;
+ --last;
+ while (isspace(CAST(unsigned char, *last)))
+ last--;
+ *++last = '\0';
+ return str;
+}
diff --git a/src/getline.c b/src/getline.c
new file mode 100644
index 0000000..21a290e
--- /dev/null
+++ b/src/getline.c
@@ -0,0 +1,104 @@
+/* $NetBSD: getline.c,v 1.2 2014/09/16 17:23:50 christos Exp $ */
+
+/*-
+ * Copyright (c) 2011 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include "file.h"
+#if !HAVE_GETLINE
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+file_public ssize_t
+getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
+{
+ char *ptr, *eptr;
+
+
+ if (*buf == NULL || *bufsiz == 0) {
+ *bufsiz = BUFSIZ;
+ if ((*buf = malloc(*bufsiz)) == NULL)
+ return -1;
+ }
+
+ for (ptr = *buf, eptr = *buf + *bufsiz;;) {
+ int c = fgetc(fp);
+ if (c == -1) {
+ if (feof(fp)) {
+ ssize_t diff = (ssize_t)(ptr - *buf);
+ if (diff != 0) {
+ *ptr = '\0';
+ return diff;
+ }
+ }
+ return -1;
+ }
+ *ptr++ = c;
+ if (c == delimiter) {
+ *ptr = '\0';
+ return ptr - *buf;
+ }
+ if (ptr + 2 >= eptr) {
+ char *nbuf;
+ size_t nbufsiz = *bufsiz * 2;
+ ssize_t d = ptr - *buf;
+ if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
+ return -1;
+ *buf = nbuf;
+ *bufsiz = nbufsiz;
+ eptr = nbuf + nbufsiz;
+ ptr = nbuf + d;
+ }
+ }
+}
+
+file_public ssize_t
+getline(char **buf, size_t *bufsiz, FILE *fp)
+{
+ return getdelim(buf, bufsiz, '\n', fp);
+}
+
+#endif
+
+#ifdef TEST
+int
+main(int argc, char *argv[])
+{
+ char *p = NULL;
+ ssize_t len;
+ size_t n = 0;
+
+ while ((len = getline(&p, &n, stdin)) != -1)
+ (void)printf("%" SIZE_T_FORMAT "d %s", len, p);
+ free(p);
+ return 0;
+}
+#endif
diff --git a/src/getopt_long.c b/src/getopt_long.c
new file mode 100644
index 0000000..cae754b
--- /dev/null
+++ b/src/getopt_long.c
@@ -0,0 +1,498 @@
+/* $NetBSD: getopt_long.c,v 1.21.4.1 2008/01/09 01:34:14 matt Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: getopt_long.c,v 1.9 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+
+#include <assert.h>
+#ifdef HAVE_ERR_H
+#include <err.h>
+#else
+#define warnx printf
+#endif
+#include <errno.h>
+#if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION)
+#include <getopt.h>
+#else
+#include "mygetopt.h"
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#define REPLACE_GETOPT
+
+#ifndef _DIAGASSERT
+#define _DIAGASSERT assert
+#endif
+
+#ifdef REPLACE_GETOPT
+#ifdef __weak_alias
+__weak_alias(getopt,_getopt)
+#endif
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = '?'; /* character checked for validity */
+int optreset; /* reset getopt */
+char *optarg; /* argument associated with option */
+#elif HAVE_NBTOOL_CONFIG_H && !HAVE_DECL_OPTRESET
+static int optreset;
+#endif
+
+#ifdef __weak_alias
+__weak_alias(getopt_long,_getopt_long)
+#endif
+
+#define IGNORE_FIRST (*options == '-' || *options == '+')
+#define PRINT_ERROR ((opterr) && ((*options != ':') \
+ || (IGNORE_FIRST && options[1] != ':')))
+#define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL)
+#define PERMUTE (!IS_POSIXLY_CORRECT && !IGNORE_FIRST)
+/* XXX: GNU ignores PC if *options == '-' */
+#define IN_ORDER (!IS_POSIXLY_CORRECT && *options == '-')
+
+/* return values */
+#define BADCH (int)'?'
+#define BADARG ((IGNORE_FIRST && options[1] == ':') \
+ || (*options == ':') ? (int)':' : (int)'?')
+#define INORDER (int)1
+
+#define EMSG ""
+
+static int getopt_internal(int, char **, const char *);
+static int gcd(int, int);
+static void permute_args(int, int, int, char **);
+
+static const char *place = EMSG; /* option letter processing */
+
+/* XXX: set optreset to 1 rather than these two */
+static int nonopt_start = -1; /* first non option argument (for permute) */
+static int nonopt_end = -1; /* first option after non options (for permute) */
+
+/* Error messages */
+static const char recargchar[] = "option requires an argument -- %c";
+static const char recargstring[] = "option requires an argument -- %s";
+static const char ambig[] = "ambiguous option -- %.*s";
+static const char noarg[] = "option doesn't take an argument -- %.*s";
+static const char illoptchar[] = "unknown option -- %c";
+static const char illoptstring[] = "unknown option -- %s";
+
+
+/*
+ * Compute the greatest common divisor of a and b.
+ */
+static int
+gcd(a, b)
+ int a;
+ int b;
+{
+ int c;
+
+ c = a % b;
+ while (c != 0) {
+ a = b;
+ b = c;
+ c = a % b;
+ }
+
+ return b;
+}
+
+/*
+ * Exchange the block from nonopt_start to nonopt_end with the block
+ * from nonopt_end to opt_end (keeping the same order of arguments
+ * in each block).
+ */
+static void
+permute_args(panonopt_start, panonopt_end, opt_end, nargv)
+ int panonopt_start;
+ int panonopt_end;
+ int opt_end;
+ char **nargv;
+{
+ int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
+ char *swap;
+
+ _DIAGASSERT(nargv != NULL);
+
+ /*
+ * compute lengths of blocks and number and size of cycles
+ */
+ nnonopts = panonopt_end - panonopt_start;
+ nopts = opt_end - panonopt_end;
+ ncycle = gcd(nnonopts, nopts);
+ cyclelen = (opt_end - panonopt_start) / ncycle;
+
+ for (i = 0; i < ncycle; i++) {
+ cstart = panonopt_end+i;
+ pos = cstart;
+ for (j = 0; j < cyclelen; j++) {
+ if (pos >= panonopt_end)
+ pos -= nnonopts;
+ else
+ pos += nopts;
+ swap = nargv[pos];
+ nargv[pos] = nargv[cstart];
+ nargv[cstart] = swap;
+ }
+ }
+}
+
+/*
+ * getopt_internal --
+ * Parse argc/argv argument vector. Called by user level routines.
+ * Returns -2 if -- is found (can be long option or end of options marker).
+ */
+static int
+getopt_internal(nargc, nargv, options)
+ int nargc;
+ char **nargv;
+ const char *options;
+{
+ char *oli; /* option letter list index */
+ int optchar;
+
+ _DIAGASSERT(nargv != NULL);
+ _DIAGASSERT(options != NULL);
+
+ optarg = NULL;
+
+ /*
+ * XXX Some programs (like rsyncd) expect to be able to
+ * XXX re-initialize optind to 0 and have getopt_long(3)
+ * XXX properly function again. Work around this braindamage.
+ */
+ if (optind == 0)
+ optind = 1;
+
+ if (optreset)
+ nonopt_start = nonopt_end = -1;
+start:
+ if (optreset || !*place) { /* update scanning pointer */
+ optreset = 0;
+ if (optind >= nargc) { /* end of argument vector */
+ place = EMSG;
+ if (nonopt_end != -1) {
+ /* do permutation, if we have to */
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ else if (nonopt_start != -1) {
+ /*
+ * If we skipped non-options, set optind
+ * to the first of them.
+ */
+ optind = nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return -1;
+ }
+ if ((*(place = nargv[optind]) != '-')
+ || (place[1] == '\0')) { /* found non-option */
+ place = EMSG;
+ if (IN_ORDER) {
+ /*
+ * GNU extension:
+ * return non-option as argument to option 1
+ */
+ optarg = nargv[optind++];
+ return INORDER;
+ }
+ if (!PERMUTE) {
+ /*
+ * if no permutation wanted, stop parsing
+ * at first non-option
+ */
+ return -1;
+ }
+ /* do permutation */
+ if (nonopt_start == -1)
+ nonopt_start = optind;
+ else if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ nonopt_start = optind -
+ (nonopt_end - nonopt_start);
+ nonopt_end = -1;
+ }
+ optind++;
+ /* process next argument */
+ goto start;
+ }
+ if (nonopt_start != -1 && nonopt_end == -1)
+ nonopt_end = optind;
+ if (place[1] && *++place == '-') { /* found "--" */
+ place++;
+ return -2;
+ }
+ }
+ if ((optchar = (int)*place++) == (int)':' ||
+ (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) {
+ /* option letter unknown or ':' */
+ if (!*place)
+ ++optind;
+ if (PRINT_ERROR)
+ warnx(illoptchar, optchar);
+ optopt = optchar;
+ return BADCH;
+ }
+ if (optchar == 'W' && oli[1] == ';') { /* -W long-option */
+ /* XXX: what if no long options provided (called by getopt)? */
+ if (*place)
+ return -2;
+
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ warnx(recargchar, optchar);
+ optopt = optchar;
+ return BADARG;
+ } else /* white space */
+ place = nargv[optind];
+ /*
+ * Handle -W arg the same as --arg (which causes getopt to
+ * stop parsing).
+ */
+ return -2;
+ }
+ if (*++oli != ':') { /* doesn't take argument */
+ if (!*place)
+ ++optind;
+ } else { /* takes (optional) argument */
+ optarg = NULL;
+ if (*place) /* no white space */
+ optarg = (char *)place;
+ /* XXX: disable test for :: if PC? (GNU doesn't) */
+ else if (oli[1] != ':') { /* arg not optional */
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ warnx(recargchar, optchar);
+ optopt = optchar;
+ return BADARG;
+ } else
+ optarg = nargv[optind];
+ }
+ place = EMSG;
+ ++optind;
+ }
+ /* dump back option letter */
+ return optchar;
+}
+
+#ifdef REPLACE_GETOPT
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ *
+ * [eventually this will replace the real getopt]
+ */
+int
+getopt(nargc, nargv, options)
+ int nargc;
+ char * const *nargv;
+ const char *options;
+{
+ int retval;
+
+ _DIAGASSERT(nargv != NULL);
+ _DIAGASSERT(options != NULL);
+
+ retval = getopt_internal(nargc, (char **)nargv, options);
+ if (retval == -2) {
+ ++optind;
+ /*
+ * We found an option (--), so if we skipped non-options,
+ * we have to permute.
+ */
+ if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end, optind,
+ (char **)nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ retval = -1;
+ }
+ return retval;
+}
+#endif
+
+/*
+ * getopt_long --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long(nargc, nargv, options, long_options, idx)
+ int nargc;
+ char * const *nargv;
+ const char *options;
+ const struct option *long_options;
+ int *idx;
+{
+ int retval;
+
+#define IDENTICAL_INTERPRETATION(_x, _y) \
+ (long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
+ long_options[(_x)].flag == long_options[(_y)].flag && \
+ long_options[(_x)].val == long_options[(_y)].val)
+
+ _DIAGASSERT(nargv != NULL);
+ _DIAGASSERT(options != NULL);
+ _DIAGASSERT(long_options != NULL);
+ /* idx may be NULL */
+
+ retval = getopt_internal(nargc, (char **)nargv, options);
+ if (retval == -2) {
+ char *current_argv, *has_equal;
+ size_t current_argv_len;
+ int i, ambiguous, match;
+
+ current_argv = (char *)place;
+ match = -1;
+ ambiguous = 0;
+
+ optind++;
+ place = EMSG;
+
+ if (*current_argv == '\0') { /* found "--" */
+ /*
+ * We found an option (--), so if we skipped
+ * non-options, we have to permute.
+ */
+ if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, (char **)nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return -1;
+ }
+ if ((has_equal = strchr(current_argv, '=')) != NULL) {
+ /* argument found (--option=arg) */
+ current_argv_len = has_equal - current_argv;
+ has_equal++;
+ } else
+ current_argv_len = strlen(current_argv);
+
+ for (i = 0; long_options[i].name; i++) {
+ /* find matching long option */
+ if (strncmp(current_argv, long_options[i].name,
+ current_argv_len))
+ continue;
+
+ if (strlen(long_options[i].name) ==
+ (unsigned)current_argv_len) {
+ /* exact match */
+ match = i;
+ ambiguous = 0;
+ break;
+ }
+ if (match == -1) /* partial match */
+ match = i;
+ else if (!IDENTICAL_INTERPRETATION(i, match))
+ ambiguous = 1;
+ }
+ if (ambiguous) {
+ /* ambiguous abbreviation */
+ if (PRINT_ERROR)
+ warnx(ambig, (int)current_argv_len,
+ current_argv);
+ optopt = 0;
+ return BADCH;
+ }
+ if (match != -1) { /* option found */
+ if (long_options[match].has_arg == no_argument
+ && has_equal) {
+ if (PRINT_ERROR)
+ warnx(noarg, (int)current_argv_len,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of
+ * flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ return BADARG;
+ }
+ if (long_options[match].has_arg == required_argument ||
+ long_options[match].has_arg == optional_argument) {
+ if (has_equal)
+ optarg = has_equal;
+ else if (long_options[match].has_arg ==
+ required_argument) {
+ /*
+ * optional argument doesn't use
+ * next nargv
+ */
+ optarg = nargv[optind++];
+ }
+ }
+ if ((long_options[match].has_arg == required_argument)
+ && (optarg == NULL)) {
+ /*
+ * Missing argument; leading ':'
+ * indicates no error should be generated
+ */
+ if (PRINT_ERROR)
+ warnx(recargstring, current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless
+ * of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ --optind;
+ return BADARG;
+ }
+ } else { /* unknown option */
+ if (PRINT_ERROR)
+ warnx(illoptstring, current_argv);
+ optopt = 0;
+ return BADCH;
+ }
+ if (long_options[match].flag) {
+ *long_options[match].flag = long_options[match].val;
+ retval = 0;
+ } else
+ retval = long_options[match].val;
+ if (idx)
+ *idx = match;
+ }
+ return retval;
+#undef IDENTICAL_INTERPRETATION
+}
diff --git a/src/gmtime_r.c b/src/gmtime_r.c
new file mode 100644
index 0000000..29e9662
--- /dev/null
+++ b/src/gmtime_r.c
@@ -0,0 +1,19 @@
+/* $File: gmtime_r.c,v 1.4 2022/09/24 20:30:13 christos Exp $ */
+
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: gmtime_r.c,v 1.4 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+#include <time.h>
+#include <string.h>
+
+/* asctime_r is not thread-safe anyway */
+struct tm *
+gmtime_r(const time_t *t, struct tm *tm)
+{
+ struct tm *tmp = gmtime(t);
+ if (tmp == NULL)
+ return NULL;
+ memcpy(tm, tmp, sizeof(*tm));
+ return tmp;
+}
diff --git a/src/is_csv.c b/src/is_csv.c
new file mode 100644
index 0000000..597d775
--- /dev/null
+++ b/src/is_csv.c
@@ -0,0 +1,196 @@
+/*-
+ * Copyright (c) 2019 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * Parse CSV object serialization format (RFC-4180, RFC-7111)
+ */
+
+#ifndef TEST
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: is_csv.c,v 1.10 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+#include <string.h>
+#include "magic.h"
+#else
+#include <sys/types.h>
+#endif
+
+
+#ifdef DEBUG
+#include <stdio.h>
+#define DPRINTF(fmt, ...) printf(fmt, __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+/*
+ * if CSV_LINES == 0:
+ * check all the lines in the buffer
+ * otherwise:
+ * check only up-to the number of lines specified
+ *
+ * the last line count is always ignored if it does not end in CRLF
+ */
+#ifndef CSV_LINES
+#define CSV_LINES 10
+#endif
+
+static int csv_parse(const unsigned char *, const unsigned char *);
+
+static const unsigned char *
+eatquote(const unsigned char *uc, const unsigned char *ue)
+{
+ int quote = 0;
+
+ while (uc < ue) {
+ unsigned char c = *uc++;
+ if (c != '"') {
+ // We already got one, done.
+ if (quote) {
+ return --uc;
+ }
+ continue;
+ }
+ if (quote) {
+ // quote-quote escapes
+ quote = 0;
+ continue;
+ }
+ // first quote
+ quote = 1;
+ }
+ return ue;
+}
+
+static int
+csv_parse(const unsigned char *uc, const unsigned char *ue)
+{
+ size_t nf = 0, tf = 0, nl = 0;
+
+ while (uc < ue) {
+ switch (*uc++) {
+ case '"':
+ // Eat until the matching quote
+ uc = eatquote(uc, ue);
+ break;
+ case ',':
+ nf++;
+ break;
+ case '\n':
+ DPRINTF("%zu %zu %zu\n", nl, nf, tf);
+ nl++;
+#if CSV_LINES
+ if (nl == CSV_LINES)
+ return tf != 0 && tf == nf;
+#endif
+ if (tf == 0) {
+ // First time and no fields, give up
+ if (nf == 0)
+ return 0;
+ // First time, set the number of fields
+ tf = nf;
+ } else if (tf != nf) {
+ // Field number mismatch, we are done.
+ return 0;
+ }
+ nf = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ return tf && nl > 2;
+}
+
+#ifndef TEST
+int
+file_is_csv(struct magic_set *ms, const struct buffer *b, int looks_text)
+{
+ const unsigned char *uc = CAST(const unsigned char *, b->fbuf);
+ const unsigned char *ue = uc + b->flen;
+ int mime = ms->flags & MAGIC_MIME;
+
+ if (!looks_text)
+ return 0;
+
+ if ((ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION)) != 0)
+ return 0;
+
+ if (!csv_parse(uc, ue))
+ return 0;
+
+ if (mime == MAGIC_MIME_ENCODING)
+ return 1;
+
+ if (mime) {
+ if (file_printf(ms, "text/csv") == -1)
+ return -1;
+ return 1;
+ }
+
+ if (file_printf(ms, "CSV text") == -1)
+ return -1;
+
+ return 1;
+}
+
+#else
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <err.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct stat st;
+ unsigned char *p;
+
+ if ((fd = open(argv[1], O_RDONLY)) == -1)
+ err(EXIT_FAILURE, "Can't open `%s'", argv[1]);
+
+ if (fstat(fd, &st) == -1)
+ err(EXIT_FAILURE, "Can't stat `%s'", argv[1]);
+
+ if ((p = CAST(char *, malloc(st.st_size))) == NULL)
+ err(EXIT_FAILURE, "Can't allocate %jd bytes",
+ (intmax_t)st.st_size);
+ if (read(fd, p, st.st_size) != st.st_size)
+ err(EXIT_FAILURE, "Can't read %jd bytes",
+ (intmax_t)st.st_size);
+ printf("is csv %d\n", csv_parse(p, p + st.st_size));
+ return 0;
+}
+#endif
diff --git a/src/is_json.c b/src/is_json.c
new file mode 100644
index 0000000..eca2a49
--- /dev/null
+++ b/src/is_json.c
@@ -0,0 +1,500 @@
+/*-
+ * Copyright (c) 2018 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * Parse JSON object serialization format (RFC-7159)
+ */
+
+#ifndef TEST
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: is_json.c,v 1.30 2022/09/27 19:12:40 christos Exp $")
+#endif
+
+#include "magic.h"
+#else
+#include <stdio.h>
+#include <stddef.h>
+#endif
+#include <string.h>
+
+#ifdef DEBUG
+#include <stdio.h>
+#define DPRINTF(a, b, c) \
+ printf("%*s%s [%.2x/%c] %.*s\n", (int)lvl, "", (a), *(b), *(b), \
+ (int)(b - c), (const char *)(c))
+#define __file_debugused
+#else
+#define DPRINTF(a, b, c) do { } while (/*CONSTCOND*/0)
+#define __file_debugused __attribute__((__unused__))
+#endif
+
+#define JSON_ARRAY 0
+#define JSON_CONSTANT 1
+#define JSON_NUMBER 2
+#define JSON_OBJECT 3
+#define JSON_STRING 4
+#define JSON_ARRAYN 5
+#define JSON_MAX 6
+
+/*
+ * if JSON_COUNT != 0:
+ * count all the objects, require that we have the whole data file
+ * otherwise:
+ * stop if we find an object or an array
+ */
+#ifndef JSON_COUNT
+#define JSON_COUNT 0
+#endif
+
+static int json_parse(const unsigned char **, const unsigned char *, size_t *,
+ size_t);
+
+static int
+json_isspace(const unsigned char uc)
+{
+ switch (uc) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int
+json_isdigit(unsigned char uc)
+{
+ switch (uc) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int
+json_isxdigit(unsigned char uc)
+{
+ if (json_isdigit(uc))
+ return 1;
+ switch (uc) {
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const unsigned char *
+json_skip_space(const unsigned char *uc, const unsigned char *ue)
+{
+ while (uc < ue && json_isspace(*uc))
+ uc++;
+ return uc;
+}
+
+/*ARGSUSED*/
+static int
+json_parse_string(const unsigned char **ucp, const unsigned char *ue,
+ size_t lvl __file_debugused)
+{
+ const unsigned char *uc = *ucp;
+ size_t i;
+
+ DPRINTF("Parse string: ", uc, *ucp);
+ while (uc < ue) {
+ switch (*uc++) {
+ case '\0':
+ goto out;
+ case '\\':
+ if (uc == ue)
+ goto out;
+ switch (*uc++) {
+ case '\0':
+ goto out;
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ continue;
+ case 'u':
+ if (ue - uc < 4) {
+ uc = ue;
+ goto out;
+ }
+ for (i = 0; i < 4; i++)
+ if (!json_isxdigit(*uc++))
+ goto out;
+ continue;
+ default:
+ goto out;
+ }
+ case '"':
+ DPRINTF("Good string: ", uc, *ucp);
+ *ucp = uc;
+ return 1;
+ default:
+ continue;
+ }
+ }
+out:
+ DPRINTF("Bad string: ", uc, *ucp);
+ *ucp = uc;
+ return 0;
+}
+
+static int
+json_parse_array(const unsigned char **ucp, const unsigned char *ue,
+ size_t *st, size_t lvl)
+{
+ const unsigned char *uc = *ucp;
+
+ DPRINTF("Parse array: ", uc, *ucp);
+ while (uc < ue) {
+ uc = json_skip_space(uc, ue);
+ if (uc == ue)
+ goto out;
+ if (*uc == ']')
+ goto done;
+ if (!json_parse(&uc, ue, st, lvl + 1))
+ goto out;
+ if (uc == ue)
+ goto out;
+ switch (*uc) {
+ case ',':
+ uc++;
+ continue;
+ case ']':
+ done:
+ st[JSON_ARRAYN]++;
+ DPRINTF("Good array: ", uc, *ucp);
+ *ucp = uc + 1;
+ return 1;
+ default:
+ goto out;
+ }
+ }
+out:
+ DPRINTF("Bad array: ", uc, *ucp);
+ *ucp = uc;
+ return 0;
+}
+
+static int
+json_parse_object(const unsigned char **ucp, const unsigned char *ue,
+ size_t *st, size_t lvl)
+{
+ const unsigned char *uc = *ucp;
+ DPRINTF("Parse object: ", uc, *ucp);
+ while (uc < ue) {
+ uc = json_skip_space(uc, ue);
+ if (uc == ue)
+ goto out;
+ if (*uc == '}') {
+ uc++;
+ goto done;
+ }
+ if (*uc++ != '"') {
+ DPRINTF("not string", uc, *ucp);
+ goto out;
+ }
+ DPRINTF("next field", uc, *ucp);
+ if (!json_parse_string(&uc, ue, lvl)) {
+ DPRINTF("not string", uc, *ucp);
+ goto out;
+ }
+ uc = json_skip_space(uc, ue);
+ if (uc == ue)
+ goto out;
+ if (*uc++ != ':') {
+ DPRINTF("not colon", uc, *ucp);
+ goto out;
+ }
+ if (!json_parse(&uc, ue, st, lvl + 1)) {
+ DPRINTF("not json", uc, *ucp);
+ goto out;
+ }
+ if (uc == ue)
+ goto out;
+ switch (*uc++) {
+ case ',':
+ continue;
+ case '}': /* { */
+ done:
+ DPRINTF("Good object: ", uc, *ucp);
+ *ucp = uc;
+ return 1;
+ default:
+ DPRINTF("not more", uc, *ucp);
+ *ucp = uc - 1;
+ goto out;
+ }
+ }
+out:
+ DPRINTF("Bad object: ", uc, *ucp);
+ *ucp = uc;
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+json_parse_number(const unsigned char **ucp, const unsigned char *ue,
+ size_t lvl __file_debugused)
+{
+ const unsigned char *uc = *ucp;
+ int got = 0;
+
+ DPRINTF("Parse number: ", uc, *ucp);
+ if (uc == ue)
+ return 0;
+ if (*uc == '-')
+ uc++;
+
+ for (; uc < ue; uc++) {
+ if (!json_isdigit(*uc))
+ break;
+ got = 1;
+ }
+ if (uc == ue)
+ goto out;
+ if (*uc == '.')
+ uc++;
+ for (; uc < ue; uc++) {
+ if (!json_isdigit(*uc))
+ break;
+ got = 1;
+ }
+ if (uc == ue)
+ goto out;
+ if (got && (*uc == 'e' || *uc == 'E')) {
+ uc++;
+ got = 0;
+ if (uc == ue)
+ goto out;
+ if (*uc == '+' || *uc == '-')
+ uc++;
+ for (; uc < ue; uc++) {
+ if (!json_isdigit(*uc))
+ break;
+ got = 1;
+ }
+ }
+out:
+ if (!got)
+ DPRINTF("Bad number: ", uc, *ucp);
+ else
+ DPRINTF("Good number: ", uc, *ucp);
+ *ucp = uc;
+ return got;
+}
+
+/*ARGSUSED*/
+static int
+json_parse_const(const unsigned char **ucp, const unsigned char *ue,
+ const char *str, size_t len, size_t lvl __file_debugused)
+{
+ const unsigned char *uc = *ucp;
+
+ DPRINTF("Parse const: ", uc, *ucp);
+ *ucp += --len - 1;
+ if (*ucp > ue)
+ *ucp = ue;
+ for (; uc < ue && --len;) {
+ if (*uc++ != *++str) {
+ DPRINTF("Bad const: ", uc, *ucp);
+ return 0;
+ }
+ }
+ DPRINTF("Good const: ", uc, *ucp);
+ return 1;
+}
+
+static int
+json_parse(const unsigned char **ucp, const unsigned char *ue,
+ size_t *st, size_t lvl)
+{
+ const unsigned char *uc, *ouc;
+ int rv = 0;
+ int t;
+
+ ouc = uc = json_skip_space(*ucp, ue);
+ if (uc == ue)
+ goto out;
+
+ // Avoid recursion
+ if (lvl > 500) {
+ DPRINTF("Too many levels", uc, *ucp);
+ return 0;
+ }
+#if JSON_COUNT
+ /* bail quickly if not counting */
+ if (lvl > 1 && (st[JSON_OBJECT] || st[JSON_ARRAYN]))
+ return 1;
+#endif
+
+ DPRINTF("Parse general: ", uc, *ucp);
+ switch (*uc++) {
+ case '"':
+ rv = json_parse_string(&uc, ue, lvl + 1);
+ t = JSON_STRING;
+ break;
+ case '[':
+ rv = json_parse_array(&uc, ue, st, lvl + 1);
+ t = JSON_ARRAY;
+ break;
+ case '{': /* '}' */
+ rv = json_parse_object(&uc, ue, st, lvl + 1);
+ t = JSON_OBJECT;
+ break;
+ case 't':
+ rv = json_parse_const(&uc, ue, "true", sizeof("true"), lvl + 1);
+ t = JSON_CONSTANT;
+ break;
+ case 'f':
+ rv = json_parse_const(&uc, ue, "false", sizeof("false"),
+ lvl + 1);
+ t = JSON_CONSTANT;
+ break;
+ case 'n':
+ rv = json_parse_const(&uc, ue, "null", sizeof("null"), lvl + 1);
+ t = JSON_CONSTANT;
+ break;
+ default:
+ --uc;
+ rv = json_parse_number(&uc, ue, lvl + 1);
+ t = JSON_NUMBER;
+ break;
+ }
+ if (rv)
+ st[t]++;
+ uc = json_skip_space(uc, ue);
+out:
+ DPRINTF("End general: ", uc, *ucp);
+ *ucp = uc;
+ if (lvl == 0) {
+ if (!rv)
+ return 0;
+ if (uc == ue)
+ return (st[JSON_ARRAYN] || st[JSON_OBJECT]) ? 1 : 0;
+ if (*ouc == *uc && json_parse(&uc, ue, st, 1))
+ return (st[JSON_ARRAYN] || st[JSON_OBJECT]) ? 2 : 0;
+ else
+ return 0;
+ }
+ return rv;
+}
+
+#ifndef TEST
+int
+file_is_json(struct magic_set *ms, const struct buffer *b)
+{
+ const unsigned char *uc = CAST(const unsigned char *, b->fbuf);
+ const unsigned char *ue = uc + b->flen;
+ size_t st[JSON_MAX];
+ int mime = ms->flags & MAGIC_MIME;
+ int jt;
+
+
+ if ((ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION)) != 0)
+ return 0;
+
+ memset(st, 0, sizeof(st));
+
+ if ((jt = json_parse(&uc, ue, st, 0)) == 0)
+ return 0;
+
+ if (mime == MAGIC_MIME_ENCODING)
+ return 1;
+ if (mime) {
+ if (file_printf(ms, "application/%s",
+ jt == 1 ? "json" : "x-ndjson") == -1)
+ return -1;
+ return 1;
+ }
+ if (file_printf(ms, "%sJSON text data",
+ jt == 1 ? "" : "New Line Delimited ") == -1)
+ return -1;
+#if JSON_COUNT
+#define P(n) st[n], st[n] > 1 ? "s" : ""
+ if (file_printf(ms, " (%" SIZE_T_FORMAT "u object%s, %" SIZE_T_FORMAT
+ "u array%s, %" SIZE_T_FORMAT "u string%s, %" SIZE_T_FORMAT
+ "u constant%s, %" SIZE_T_FORMAT "u number%s, %" SIZE_T_FORMAT
+ "u >1array%s)",
+ P(JSON_OBJECT), P(JSON_ARRAY), P(JSON_STRING), P(JSON_CONSTANT),
+ P(JSON_NUMBER), P(JSON_ARRAYN))
+ == -1)
+ return -1;
+#endif
+ return 1;
+}
+
+#else
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <err.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct stat st;
+ unsigned char *p;
+ size_t stats[JSON_MAX];
+
+ if ((fd = open(argv[1], O_RDONLY)) == -1)
+ err(EXIT_FAILURE, "Can't open `%s'", argv[1]);
+
+ if (fstat(fd, &st) == -1)
+ err(EXIT_FAILURE, "Can't stat `%s'", argv[1]);
+
+ if ((p = CAST(char *, malloc(st.st_size))) == NULL)
+ err(EXIT_FAILURE, "Can't allocate %jd bytes",
+ (intmax_t)st.st_size);
+ if (read(fd, p, st.st_size) != st.st_size)
+ err(EXIT_FAILURE, "Can't read %jd bytes",
+ (intmax_t)st.st_size);
+ memset(stats, 0, sizeof(stats));
+ printf("is json %d\n", json_parse((const unsigned char **)&p,
+ p + st.st_size, stats, 0));
+ return 0;
+}
+#endif
diff --git a/src/is_tar.c b/src/is_tar.c
new file mode 100644
index 0000000..fa83e1e
--- /dev/null
+++ b/src/is_tar.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * is_tar() -- figure out whether file is a tar archive.
+ *
+ * Stolen (by the author!) from the file_public domain tar program:
+ * Public Domain version written 26 Aug 1985 John Gilmore (ihnp4!hoptoad!gnu).
+ *
+ * @(#)list.c 1.18 9/23/86 Public Domain - gnu
+ *
+ * Comments changed and some code/comments reformatted
+ * for file command by Ian Darwin.
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: is_tar.c,v 1.50 2022/12/26 17:31:14 christos Exp $")
+#endif
+
+#include "magic.h"
+#include <string.h>
+#include <ctype.h>
+#include "tar.h"
+
+#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
+
+file_private int is_tar(const unsigned char *, size_t);
+file_private int from_oct(const char *, size_t); /* Decode octal number */
+
+static const char tartype[][32] = { /* should be equal to messages */
+ "tar archive", /* found in ../magic/Magdir/archive */
+ "POSIX tar archive",
+ "POSIX tar archive (GNU)", /* */
+};
+
+file_protected int
+file_is_tar(struct magic_set *ms, const struct buffer *b)
+{
+ const unsigned char *buf = CAST(const unsigned char *, b->fbuf);
+ size_t nbytes = b->flen;
+ /*
+ * Do the tar test first, because if the first file in the tar
+ * archive starts with a dot, we can confuse it with an nroff file.
+ */
+ int tar;
+ int mime = ms->flags & MAGIC_MIME;
+
+ if ((ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION)) != 0)
+ return 0;
+
+ tar = is_tar(buf, nbytes);
+ if (tar < 1 || tar > 3)
+ return 0;
+
+ if (mime == MAGIC_MIME_ENCODING)
+ return 1;
+
+ if (file_printf(ms, "%s", mime ? "application/x-tar" :
+ tartype[tar - 1]) == -1)
+ return -1;
+
+ return 1;
+}
+
+/*
+ * Return
+ * 0 if the checksum is bad (i.e., probably not a tar archive),
+ * 1 for old UNIX tar file,
+ * 2 for Unix Std (POSIX) tar file,
+ * 3 for GNU tar file.
+ */
+file_private int
+is_tar(const unsigned char *buf, size_t nbytes)
+{
+ static const char gpkg_match[] = "/gpkg-1";
+
+ const union record *header = RCAST(const union record *,
+ RCAST(const void *, buf));
+ size_t i;
+ int sum, recsum;
+ const unsigned char *p, *ep;
+ const char *nulp;
+
+ if (nbytes < sizeof(*header))
+ return 0;
+
+ /* If the file looks like Gentoo GLEP 78 binary package (GPKG),
+ * don't waste time on further checks and fall back to magic rules.
+ */
+ nulp = CAST(const char *,
+ memchr(header->header.name, 0, sizeof(header->header.name)));
+ if (nulp != NULL && nulp >= header->header.name + sizeof(gpkg_match) &&
+ memcmp(nulp - sizeof(gpkg_match) + 1, gpkg_match,
+ sizeof(gpkg_match)) == 0)
+ return 0;
+
+ recsum = from_oct(header->header.chksum, sizeof(header->header.chksum));
+
+ sum = 0;
+ p = header->charptr;
+ ep = header->charptr + sizeof(*header);
+ while (p < ep)
+ sum += *p++;
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = 0; i < sizeof(header->header.chksum); i++)
+ sum -= header->header.chksum[i];
+ sum += ' ' * sizeof(header->header.chksum);
+
+ if (sum != recsum)
+ return 0; /* Not a tar archive */
+
+ if (strncmp(header->header.magic, GNUTMAGIC,
+ sizeof(header->header.magic)) == 0)
+ return 3; /* GNU Unix Standard tar archive */
+
+ if (strncmp(header->header.magic, TMAGIC,
+ sizeof(header->header.magic)) == 0)
+ return 2; /* Unix Standard tar archive */
+
+ return 1; /* Old fashioned tar archive */
+}
+
+
+/*
+ * Quick and dirty octal conversion.
+ *
+ * Result is -1 if the field is invalid (all blank, or non-octal).
+ */
+file_private int
+from_oct(const char *where, size_t digs)
+{
+ int value;
+
+ if (digs == 0)
+ return -1;
+
+ while (isspace(CAST(unsigned char, *where))) { /* Skip spaces */
+ where++;
+ if (digs-- == 0)
+ return -1; /* All blank field */
+ }
+ value = 0;
+ while (digs > 0 && isodigit(*where)) { /* Scan til non-octal */
+ value = (value << 3) | (*where++ - '0');
+ digs--;
+ }
+
+ if (digs > 0 && *where && !isspace(CAST(unsigned char, *where)))
+ return -1; /* Ended on non-(space/NUL) */
+
+ return value;
+}
diff --git a/src/localtime_r.c b/src/localtime_r.c
new file mode 100644
index 0000000..8d3032a
--- /dev/null
+++ b/src/localtime_r.c
@@ -0,0 +1,19 @@
+/* $File: localtime_r.c,v 1.4 2022/09/24 20:30:13 christos Exp $ */
+
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: localtime_r.c,v 1.4 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+#include <time.h>
+#include <string.h>
+
+/* asctime_r is not thread-safe anyway */
+struct tm *
+localtime_r(const time_t *t, struct tm *tm)
+{
+ struct tm *tmp = localtime(t);
+ if (tmp == NULL)
+ return NULL;
+ memcpy(tm, tmp, sizeof(*tm));
+ return tmp;
+}
diff --git a/src/magic.c b/src/magic.c
new file mode 100644
index 0000000..80555da
--- /dev/null
+++ b/src/magic.c
@@ -0,0 +1,680 @@
+/*
+ * Copyright (c) Christos Zoulas 2003.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifdef WIN32
+#include <windows.h>
+#include <shlwapi.h>
+#endif
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: magic.c,v 1.120 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#ifdef QUICK
+#include <sys/mman.h>
+#endif
+#include <limits.h> /* for PIPE_BUF */
+
+#if defined(HAVE_UTIMES)
+# include <sys/time.h>
+#elif defined(HAVE_UTIME)
+# if defined(HAVE_SYS_UTIME_H)
+# include <sys/utime.h>
+# elif defined(HAVE_UTIME_H)
+# include <utime.h>
+# endif
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* for read() */
+#endif
+
+#ifndef PIPE_BUF
+/* Get the PIPE_BUF from pathconf */
+#ifdef _PC_PIPE_BUF
+#define PIPE_BUF pathconf(".", _PC_PIPE_BUF)
+#else
+#define PIPE_BUF 512
+#endif
+#endif
+
+file_private void close_and_restore(const struct magic_set *, const char *, int,
+ const struct stat *);
+file_private int unreadable_info(struct magic_set *, mode_t, const char *);
+file_private const char* get_default_magic(void);
+#ifndef COMPILE_ONLY
+file_private const char *file_or_fd(struct magic_set *, const char *, int);
+#endif
+
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+
+#ifdef WIN32
+/* HINSTANCE of this shared library. Needed for get_default_magic() */
+static HINSTANCE _w32_dll_instance = NULL;
+
+static void
+_w32_append_path(char **hmagicpath, const char *fmt, ...)
+{
+ char *tmppath;
+ char *newpath;
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (vasprintf(&tmppath, fmt, ap) < 0) {
+ va_end(ap);
+ return;
+ }
+ va_end(ap);
+
+ if (access(tmppath, R_OK) == -1)
+ goto out;
+
+ if (*hmagicpath == NULL) {
+ *hmagicpath = tmppath;
+ return;
+ }
+
+ if (asprintf(&newpath, "%s%c%s", *hmagicpath, PATHSEP, tmppath) < 0)
+ goto out;
+
+ free(*hmagicpath);
+ free(tmppath);
+ *hmagicpath = newpath;
+ return;
+out:
+ free(tmppath);
+}
+
+static void
+_w32_get_magic_relative_to(char **hmagicpath, HINSTANCE module)
+{
+ static const char *trypaths[] = {
+ "%s/share/misc/magic.mgc",
+ "%s/magic.mgc",
+ };
+ LPSTR dllpath;
+ size_t sp;
+
+ dllpath = calloc(MAX_PATH + 1, sizeof(*dllpath));
+
+ if (!GetModuleFileNameA(module, dllpath, MAX_PATH))
+ goto out;
+
+ PathRemoveFileSpecA(dllpath);
+
+ if (module) {
+ char exepath[MAX_PATH];
+ GetModuleFileNameA(NULL, exepath, MAX_PATH);
+ PathRemoveFileSpecA(exepath);
+ if (stricmp(exepath, dllpath) == 0)
+ goto out;
+ }
+
+ sp = strlen(dllpath);
+ if (sp > 3 && stricmp(&dllpath[sp - 3], "bin") == 0) {
+ _w32_append_path(hmagicpath,
+ "%s/../share/misc/magic.mgc", dllpath);
+ goto out;
+ }
+
+ for (sp = 0; sp < __arraycount(trypaths); sp++)
+ _w32_append_path(hmagicpath, trypaths[sp], dllpath);
+out:
+ free(dllpath);
+}
+
+#ifndef BUILD_AS_WINDOWS_STATIC_LIBARAY
+/* Placate GCC by offering a sacrificial previous prototype */
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
+
+BOOL WINAPI
+DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
+ LPVOID lpvReserved __attribute__((__unused__)))
+{
+ if (fdwReason == DLL_PROCESS_ATTACH)
+ _w32_dll_instance = hinstDLL;
+ return 1;
+}
+#endif
+#endif
+
+file_private const char *
+get_default_magic(void)
+{
+ static const char hmagic[] = "/.magic/magic.mgc";
+ static char *default_magic;
+ char *home, *hmagicpath;
+
+#ifndef WIN32
+ struct stat st;
+
+ if (default_magic) {
+ free(default_magic);
+ default_magic = NULL;
+ }
+ if ((home = getenv("HOME")) == NULL)
+ return MAGIC;
+
+ if (asprintf(&hmagicpath, "%s/.magic.mgc", home) < 0)
+ return MAGIC;
+ if (stat(hmagicpath, &st) == -1) {
+ free(hmagicpath);
+ if (asprintf(&hmagicpath, "%s/.magic", home) < 0)
+ return MAGIC;
+ if (stat(hmagicpath, &st) == -1)
+ goto out;
+ if (S_ISDIR(st.st_mode)) {
+ free(hmagicpath);
+ if (asprintf(&hmagicpath, "%s/%s", home, hmagic) < 0)
+ return MAGIC;
+ if (access(hmagicpath, R_OK) == -1)
+ goto out;
+ }
+ }
+
+ if (asprintf(&default_magic, "%s:%s", hmagicpath, MAGIC) < 0)
+ goto out;
+ free(hmagicpath);
+ return default_magic;
+out:
+ default_magic = NULL;
+ free(hmagicpath);
+ return MAGIC;
+#else
+ hmagicpath = NULL;
+
+ if (default_magic) {
+ free(default_magic);
+ default_magic = NULL;
+ }
+
+ /* Before anything else, try to get a magic file from user HOME */
+ if ((home = getenv("HOME")) != NULL)
+ _w32_append_path(&hmagicpath, "%s%s", home, hmagic);
+
+ /* First, try to get a magic file from user-application data */
+ if ((home = getenv("LOCALAPPDATA")) != NULL)
+ _w32_append_path(&hmagicpath, "%s%s", home, hmagic);
+
+ /* Second, try to get a magic file from the user profile data */
+ if ((home = getenv("USERPROFILE")) != NULL)
+ _w32_append_path(&hmagicpath,
+ "%s/Local Settings/Application Data%s", home, hmagic);
+
+ /* Third, try to get a magic file from Common Files */
+ if ((home = getenv("COMMONPROGRAMFILES")) != NULL)
+ _w32_append_path(&hmagicpath, "%s%s", home, hmagic);
+
+ /* Fourth, try to get magic file relative to exe location */
+ _w32_get_magic_relative_to(&hmagicpath, NULL);
+
+ /* Fifth, try to get magic file relative to dll location */
+ _w32_get_magic_relative_to(&hmagicpath, _w32_dll_instance);
+
+ /* Avoid MAGIC constant - it likely points to a file within MSys tree */
+ default_magic = hmagicpath;
+ return default_magic;
+#endif
+}
+
+file_public const char *
+magic_getpath(const char *magicfile, int action)
+{
+ if (magicfile != NULL)
+ return magicfile;
+
+ magicfile = getenv("MAGIC");
+ if (magicfile != NULL)
+ return magicfile;
+
+ return action == FILE_LOAD ? get_default_magic() : MAGIC;
+}
+
+file_public struct magic_set *
+magic_open(int flags)
+{
+ return file_ms_alloc(flags);
+}
+
+file_private int
+unreadable_info(struct magic_set *ms, mode_t md, const char *file)
+{
+ if (file) {
+ /* We cannot open it, but we were able to stat it. */
+ if (access(file, W_OK) == 0)
+ if (file_printf(ms, "writable, ") == -1)
+ return -1;
+#ifndef WIN32
+ if (access(file, X_OK) == 0)
+ if (file_printf(ms, "executable, ") == -1)
+ return -1;
+#else
+ /* X_OK doesn't work well on MS-Windows */
+ {
+ const char *p = strrchr(file, '.');
+ if (p && (stricmp(p, ".exe")
+ || stricmp(p, ".dll")
+ || stricmp(p, ".bat")
+ || stricmp(p, ".cmd")))
+ if (file_printf(ms, "writable, ") == -1)
+ return -1;
+ }
+#endif
+ }
+ if (S_ISREG(md))
+ if (file_printf(ms, "regular file, ") == -1)
+ return -1;
+ if (file_printf(ms, "no read permission") == -1)
+ return -1;
+ return 0;
+}
+
+file_public void
+magic_close(struct magic_set *ms)
+{
+ if (ms == NULL)
+ return;
+ file_ms_free(ms);
+}
+
+/*
+ * load a magic file
+ */
+file_public int
+magic_load(struct magic_set *ms, const char *magicfile)
+{
+ if (ms == NULL)
+ return -1;
+ return file_apprentice(ms, magicfile, FILE_LOAD);
+}
+
+#ifndef COMPILE_ONLY
+/*
+ * Install a set of compiled magic buffers.
+ */
+file_public int
+magic_load_buffers(struct magic_set *ms, void **bufs, size_t *sizes,
+ size_t nbufs)
+{
+ if (ms == NULL)
+ return -1;
+ return buffer_apprentice(ms, RCAST(struct magic **, bufs),
+ sizes, nbufs);
+}
+#endif
+
+file_public int
+magic_compile(struct magic_set *ms, const char *magicfile)
+{
+ if (ms == NULL)
+ return -1;
+ return file_apprentice(ms, magicfile, FILE_COMPILE);
+}
+
+file_public int
+magic_check(struct magic_set *ms, const char *magicfile)
+{
+ if (ms == NULL)
+ return -1;
+ return file_apprentice(ms, magicfile, FILE_CHECK);
+}
+
+file_public int
+magic_list(struct magic_set *ms, const char *magicfile)
+{
+ if (ms == NULL)
+ return -1;
+ return file_apprentice(ms, magicfile, FILE_LIST);
+}
+
+file_private void
+close_and_restore(const struct magic_set *ms, const char *name, int fd,
+ const struct stat *sb)
+{
+ if (fd == STDIN_FILENO || name == NULL)
+ return;
+ (void) close(fd);
+
+ if ((ms->flags & MAGIC_PRESERVE_ATIME) != 0) {
+ /*
+ * Try to restore access, modification times if read it.
+ * This is really *bad* because it will modify the status
+ * time of the file... And of course this will affect
+ * backup programs
+ */
+#ifdef HAVE_UTIMES
+ struct timeval utsbuf[2];
+ (void)memset(utsbuf, 0, sizeof(utsbuf));
+ utsbuf[0].tv_sec = sb->st_atime;
+ utsbuf[1].tv_sec = sb->st_mtime;
+
+ (void) utimes(name, utsbuf); /* don't care if loses */
+#elif defined(HAVE_UTIME_H) || defined(HAVE_SYS_UTIME_H)
+ struct utimbuf utbuf;
+
+ (void)memset(&utbuf, 0, sizeof(utbuf));
+ utbuf.actime = sb->st_atime;
+ utbuf.modtime = sb->st_mtime;
+ (void) utime(name, &utbuf); /* don't care if loses */
+#endif
+ }
+}
+
+#ifndef COMPILE_ONLY
+
+/*
+ * find type of descriptor
+ */
+file_public const char *
+magic_descriptor(struct magic_set *ms, int fd)
+{
+ if (ms == NULL)
+ return NULL;
+ return file_or_fd(ms, NULL, fd);
+}
+
+/*
+ * find type of named file
+ */
+file_public const char *
+magic_file(struct magic_set *ms, const char *inname)
+{
+ if (ms == NULL)
+ return NULL;
+ return file_or_fd(ms, inname, STDIN_FILENO);
+}
+
+file_private const char *
+file_or_fd(struct magic_set *ms, const char *inname, int fd)
+{
+ int rv = -1;
+ unsigned char *buf;
+ struct stat sb;
+ ssize_t nbytes = 0; /* number of bytes read from a datafile */
+ int ispipe = 0;
+ int okstat = 0;
+ off_t pos = CAST(off_t, -1);
+
+ if (file_reset(ms, 1) == -1)
+ goto out;
+
+ /*
+ * one extra for terminating '\0', and
+ * some overlapping space for matches near EOF
+ */
+#define SLOP (1 + sizeof(union VALUETYPE))
+ if ((buf = CAST(unsigned char *, malloc(ms->bytes_max + SLOP))) == NULL)
+ return NULL;
+
+ switch (file_fsmagic(ms, inname, &sb)) {
+ case -1: /* error */
+ goto done;
+ case 0: /* nothing found */
+ break;
+ default: /* matched it and printed type */
+ rv = 0;
+ goto done;
+ }
+
+#ifdef WIN32
+ /* Place stdin in binary mode, so EOF (Ctrl+Z) doesn't stop early. */
+ if (fd == STDIN_FILENO)
+ _setmode(STDIN_FILENO, O_BINARY);
+#endif
+ if (inname != NULL) {
+ int flags = O_RDONLY|O_BINARY|O_NONBLOCK|O_CLOEXEC;
+ errno = 0;
+ if ((fd = open(inname, flags)) < 0) {
+ okstat = stat(inname, &sb) == 0;
+#ifdef WIN32
+ /*
+ * Can't stat, can't open. It may have been opened in
+ * fsmagic, so if the user doesn't have read permission,
+ * allow it to say so; otherwise an error was probably
+ * displayed in fsmagic.
+ */
+ if (!okstat && errno == EACCES) {
+ sb.st_mode = S_IFBLK;
+ okstat = 1;
+ }
+#endif
+ if (okstat &&
+ unreadable_info(ms, sb.st_mode, inname) == -1)
+ goto done;
+ rv = 0;
+ goto done;
+ }
+#if O_CLOEXEC == 0 && defined(F_SETFD)
+ (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+ }
+
+ if (fd != -1) {
+ okstat = fstat(fd, &sb) == 0;
+ if (okstat && S_ISFIFO(sb.st_mode))
+ ispipe = 1;
+ if (inname == NULL)
+ pos = lseek(fd, CAST(off_t, 0), SEEK_CUR);
+ }
+
+ /*
+ * try looking at the first ms->bytes_max bytes
+ */
+ if (ispipe) {
+ if (fd != -1) {
+ ssize_t r = 0;
+
+ while ((r = sread(fd, RCAST(void *, &buf[nbytes]),
+ CAST(size_t, ms->bytes_max - nbytes), 1)) > 0) {
+ nbytes += r;
+ if (r < PIPE_BUF) break;
+ }
+ }
+
+ if (nbytes == 0 && inname) {
+ /* We can not read it, but we were able to stat it. */
+ if (unreadable_info(ms, sb.st_mode, inname) == -1)
+ goto done;
+ rv = 0;
+ goto done;
+ }
+
+ } else if (fd != -1) {
+ /* Windows refuses to read from a big console buffer. */
+ size_t howmany =
+#ifdef WIN32
+ _isatty(fd) ? 8 * 1024 :
+#endif
+ ms->bytes_max;
+ if ((nbytes = read(fd, RCAST(void *, buf), howmany)) == -1) {
+ if (inname == NULL && fd != STDIN_FILENO)
+ file_error(ms, errno, "cannot read fd %d", fd);
+ else
+ file_error(ms, errno, "cannot read `%s'",
+ inname == NULL ? "/dev/stdin" : inname);
+ goto done;
+ }
+ }
+
+ (void)memset(buf + nbytes, 0, SLOP); /* NUL terminate */
+ if (file_buffer(ms, fd, okstat ? &sb : NULL, inname, buf, CAST(size_t, nbytes)) == -1)
+ goto done;
+ rv = 0;
+done:
+ free(buf);
+ if (fd != -1) {
+ if (pos != CAST(off_t, -1))
+ (void)lseek(fd, pos, SEEK_SET);
+ close_and_restore(ms, inname, fd, &sb);
+ }
+out:
+ return rv == 0 ? file_getbuffer(ms) : NULL;
+}
+
+
+file_public const char *
+magic_buffer(struct magic_set *ms, const void *buf, size_t nb)
+{
+ if (ms == NULL)
+ return NULL;
+ if (file_reset(ms, 1) == -1)
+ return NULL;
+ /*
+ * The main work is done here!
+ * We have the file name and/or the data buffer to be identified.
+ */
+ if (file_buffer(ms, -1, NULL, NULL, buf, nb) == -1) {
+ return NULL;
+ }
+ return file_getbuffer(ms);
+}
+#endif
+
+file_public const char *
+magic_error(struct magic_set *ms)
+{
+ if (ms == NULL)
+ return "Magic database is not open";
+ return (ms->event_flags & EVENT_HAD_ERR) ? ms->o.buf : NULL;
+}
+
+file_public int
+magic_errno(struct magic_set *ms)
+{
+ if (ms == NULL)
+ return EINVAL;
+ return (ms->event_flags & EVENT_HAD_ERR) ? ms->error : 0;
+}
+
+file_public int
+magic_getflags(struct magic_set *ms)
+{
+ if (ms == NULL)
+ return -1;
+
+ return ms->flags;
+}
+
+file_public int
+magic_setflags(struct magic_set *ms, int flags)
+{
+ if (ms == NULL)
+ return -1;
+#if !defined(HAVE_UTIME) && !defined(HAVE_UTIMES)
+ if (flags & MAGIC_PRESERVE_ATIME)
+ return -1;
+#endif
+ ms->flags = flags;
+ return 0;
+}
+
+file_public int
+magic_version(void)
+{
+ return MAGIC_VERSION;
+}
+
+file_public int
+magic_setparam(struct magic_set *ms, int param, const void *val)
+{
+ if (ms == NULL)
+ return -1;
+ switch (param) {
+ case MAGIC_PARAM_INDIR_MAX:
+ ms->indir_max = CAST(uint16_t, *CAST(const size_t *, val));
+ return 0;
+ case MAGIC_PARAM_NAME_MAX:
+ ms->name_max = CAST(uint16_t, *CAST(const size_t *, val));
+ return 0;
+ case MAGIC_PARAM_ELF_PHNUM_MAX:
+ ms->elf_phnum_max = CAST(uint16_t, *CAST(const size_t *, val));
+ return 0;
+ case MAGIC_PARAM_ELF_SHNUM_MAX:
+ ms->elf_shnum_max = CAST(uint16_t, *CAST(const size_t *, val));
+ return 0;
+ case MAGIC_PARAM_ELF_NOTES_MAX:
+ ms->elf_notes_max = CAST(uint16_t, *CAST(const size_t *, val));
+ return 0;
+ case MAGIC_PARAM_REGEX_MAX:
+ ms->regex_max = CAST(uint16_t, *CAST(const size_t *, val));
+ return 0;
+ case MAGIC_PARAM_BYTES_MAX:
+ ms->bytes_max = *CAST(const size_t *, val);
+ return 0;
+ case MAGIC_PARAM_ENCODING_MAX:
+ ms->encoding_max = *CAST(const size_t *, val);
+ return 0;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+file_public int
+magic_getparam(struct magic_set *ms, int param, void *val)
+{
+ if (ms == NULL)
+ return -1;
+ switch (param) {
+ case MAGIC_PARAM_INDIR_MAX:
+ *CAST(size_t *, val) = ms->indir_max;
+ return 0;
+ case MAGIC_PARAM_NAME_MAX:
+ *CAST(size_t *, val) = ms->name_max;
+ return 0;
+ case MAGIC_PARAM_ELF_PHNUM_MAX:
+ *CAST(size_t *, val) = ms->elf_phnum_max;
+ return 0;
+ case MAGIC_PARAM_ELF_SHNUM_MAX:
+ *CAST(size_t *, val) = ms->elf_shnum_max;
+ return 0;
+ case MAGIC_PARAM_ELF_NOTES_MAX:
+ *CAST(size_t *, val) = ms->elf_notes_max;
+ return 0;
+ case MAGIC_PARAM_REGEX_MAX:
+ *CAST(size_t *, val) = ms->regex_max;
+ return 0;
+ case MAGIC_PARAM_BYTES_MAX:
+ *CAST(size_t *, val) = ms->bytes_max;
+ return 0;
+ case MAGIC_PARAM_ENCODING_MAX:
+ *CAST(size_t *, val) = ms->encoding_max;
+ return 0;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
diff --git a/src/magic.h.in b/src/magic.h.in
new file mode 100644
index 0000000..bed6a04
--- /dev/null
+++ b/src/magic.h.in
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) Christos Zoulas 2003.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+#ifndef _MAGIC_H
+#define _MAGIC_H
+
+#include <sys/types.h>
+
+#define MAGIC_NONE 0x0000000 /* No flags */
+#define MAGIC_DEBUG 0x0000001 /* Turn on debugging */
+#define MAGIC_SYMLINK 0x0000002 /* Follow symlinks */
+#define MAGIC_COMPRESS 0x0000004 /* Check inside compressed files */
+#define MAGIC_DEVICES 0x0000008 /* Look at the contents of devices */
+#define MAGIC_MIME_TYPE 0x0000010 /* Return the MIME type */
+#define MAGIC_CONTINUE 0x0000020 /* Return all matches */
+#define MAGIC_CHECK 0x0000040 /* Print warnings to stderr */
+#define MAGIC_PRESERVE_ATIME 0x0000080 /* Restore access time on exit */
+#define MAGIC_RAW 0x0000100 /* Don't convert unprintable chars */
+#define MAGIC_ERROR 0x0000200 /* Handle ENOENT etc as real errors */
+#define MAGIC_MIME_ENCODING 0x0000400 /* Return the MIME encoding */
+#define MAGIC_MIME (MAGIC_MIME_TYPE|MAGIC_MIME_ENCODING)
+#define MAGIC_APPLE 0x0000800 /* Return the Apple creator/type */
+#define MAGIC_EXTENSION 0x1000000 /* Return a /-separated list of
+ * extensions */
+#define MAGIC_COMPRESS_TRANSP 0x2000000 /* Check inside compressed files
+ * but not report compression */
+#define MAGIC_NO_COMPRESS_FORK 0x4000000 /* Don't allow decompression that
+ * needs to fork */
+#define MAGIC_NODESC (MAGIC_EXTENSION|MAGIC_MIME|MAGIC_APPLE)
+
+#define MAGIC_NO_CHECK_COMPRESS 0x0001000 /* Don't check for compressed files */
+#define MAGIC_NO_CHECK_TAR 0x0002000 /* Don't check for tar files */
+#define MAGIC_NO_CHECK_SOFT 0x0004000 /* Don't check magic entries */
+#define MAGIC_NO_CHECK_APPTYPE 0x0008000 /* Don't check application type */
+#define MAGIC_NO_CHECK_ELF 0x0010000 /* Don't check for elf details */
+#define MAGIC_NO_CHECK_TEXT 0x0020000 /* Don't check for text files */
+#define MAGIC_NO_CHECK_CDF 0x0040000 /* Don't check for cdf files */
+#define MAGIC_NO_CHECK_CSV 0x0080000 /* Don't check for CSV files */
+#define MAGIC_NO_CHECK_TOKENS 0x0100000 /* Don't check tokens */
+#define MAGIC_NO_CHECK_ENCODING 0x0200000 /* Don't check text encodings */
+#define MAGIC_NO_CHECK_JSON 0x0400000 /* Don't check for JSON files */
+
+/* No built-in tests; only consult the magic file */
+#define MAGIC_NO_CHECK_BUILTIN ( \
+ MAGIC_NO_CHECK_COMPRESS | \
+ MAGIC_NO_CHECK_TAR | \
+/* MAGIC_NO_CHECK_SOFT | */ \
+ MAGIC_NO_CHECK_APPTYPE | \
+ MAGIC_NO_CHECK_ELF | \
+ MAGIC_NO_CHECK_TEXT | \
+ MAGIC_NO_CHECK_CSV | \
+ MAGIC_NO_CHECK_CDF | \
+ MAGIC_NO_CHECK_TOKENS | \
+ MAGIC_NO_CHECK_ENCODING | \
+ MAGIC_NO_CHECK_JSON | \
+ 0 \
+)
+
+#define MAGIC_SNPRINTB "\177\020\
+b\0debug\0\
+b\1symlink\0\
+b\2compress\0\
+b\3devices\0\
+b\4mime_type\0\
+b\5continue\0\
+b\6check\0\
+b\7preserve_atime\0\
+b\10raw\0\
+b\11error\0\
+b\12mime_encoding\0\
+b\13apple\0\
+b\14no_check_compress\0\
+b\15no_check_tar\0\
+b\16no_check_soft\0\
+b\17no_check_sapptype\0\
+b\20no_check_elf\0\
+b\21no_check_text\0\
+b\22no_check_cdf\0\
+b\23no_check_reserved0\0\
+b\24no_check_tokens\0\
+b\25no_check_encoding\0\
+b\26no_check_json\0\
+b\27no_check_reserved2\0\
+b\30extension\0\
+b\31transp_compression\0\
+"
+
+/* Defined for backwards compatibility (renamed) */
+#define MAGIC_NO_CHECK_ASCII MAGIC_NO_CHECK_TEXT
+
+/* Defined for backwards compatibility; do nothing */
+#define MAGIC_NO_CHECK_FORTRAN 0x000000 /* Don't check ascii/fortran */
+#define MAGIC_NO_CHECK_TROFF 0x000000 /* Don't check ascii/troff */
+
+#define MAGIC_VERSION X.YY /* This implementation */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct magic_set *magic_t;
+magic_t magic_open(int);
+void magic_close(magic_t);
+
+const char *magic_getpath(const char *, int);
+const char *magic_file(magic_t, const char *);
+const char *magic_descriptor(magic_t, int);
+const char *magic_buffer(magic_t, const void *, size_t);
+
+const char *magic_error(magic_t);
+int magic_getflags(magic_t);
+int magic_setflags(magic_t, int);
+
+int magic_version(void);
+int magic_load(magic_t, const char *);
+int magic_load_buffers(magic_t, void **, size_t *, size_t);
+
+int magic_compile(magic_t, const char *);
+int magic_check(magic_t, const char *);
+int magic_list(magic_t, const char *);
+int magic_errno(magic_t);
+
+#define MAGIC_PARAM_INDIR_MAX 0
+#define MAGIC_PARAM_NAME_MAX 1
+#define MAGIC_PARAM_ELF_PHNUM_MAX 2
+#define MAGIC_PARAM_ELF_SHNUM_MAX 3
+#define MAGIC_PARAM_ELF_NOTES_MAX 4
+#define MAGIC_PARAM_REGEX_MAX 5
+#define MAGIC_PARAM_BYTES_MAX 6
+#define MAGIC_PARAM_ENCODING_MAX 7
+
+int magic_setparam(magic_t, int, const void *);
+int magic_getparam(magic_t, int, void *);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* _MAGIC_H */
diff --git a/src/memtest.c b/src/memtest.c
new file mode 100644
index 0000000..da0f10b
--- /dev/null
+++ b/src/memtest.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) Christos Zoulas 2021.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: memtest.c,v 1.6 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <magic.h>
+
+void *
+malloc(size_t len)
+{
+ char buf[512];
+ void *(*orig)(size_t) = dlsym(RTLD_NEXT, "malloc");
+ void *p = (*orig)(len);
+ int l = snprintf(buf, sizeof(buf), "malloc %zu %p\n", len, p);
+ write(2, buf, l);
+ return p;
+}
+
+void
+free(void *p)
+{
+ char buf[512];
+ void (*orig)(void *) = dlsym(RTLD_NEXT, "free");
+ (*orig)(p);
+ int l = snprintf(buf, sizeof(buf), "free %p\n", p);
+ write(2, buf, l);
+}
+
+void *
+calloc(size_t len, size_t nitems)
+{
+ char buf[512];
+ void *(*orig)(size_t, size_t) = dlsym(RTLD_NEXT, "calloc");
+ void *p = (*orig)(len, nitems);
+ size_t tot = len * nitems;
+ int l = snprintf(buf, sizeof(buf), "calloc %zu %p\n", tot, p);
+ write(2, buf, l);
+ return p;
+}
+void *
+realloc(void *q, size_t len)
+{
+ char buf[512];
+ void *(*orig)(void *, size_t) = dlsym(RTLD_NEXT, "realloc");
+ void *p = (*orig)(q, len);
+ int l = snprintf(buf, sizeof(buf), "realloc %zu %p\n", len, p);
+ write(2, buf, l);
+ return p;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: test [-b] <filename>\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ bool buf = false;
+ int c;
+
+ while ((c = getopt(argc, argv, "b")) != -1)
+ switch (c) {
+ case 'b':
+ buf = true;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ magic_t m = magic_open(0);
+ if (m == NULL)
+ err(EXIT_FAILURE, "magic_open");
+
+ magic_load(m, NULL);
+
+ const char *r;
+ if (buf) {
+ int fd = open(argv[0], O_RDONLY);
+ if (fd == -1)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+
+ struct stat st;
+ if (fstat(fd, &st) == -1)
+ err(EXIT_FAILURE, "Cannot stat `%s'", argv[0]);
+ size_t l = (size_t)st.st_size;
+ void *p = mmap(NULL, l, PROT_READ, MAP_FILE | MAP_PRIVATE, fd,
+ (off_t)0);
+ if (p == MAP_FAILED)
+ err(EXIT_FAILURE, "Cannot map `%s'", argv[0]);
+ close(fd);
+ r = magic_buffer(m, p, l);
+ munmap(p, l);
+ } else {
+ r = magic_file(m, argv[0]);
+ }
+ magic_close(m);
+
+ printf("%s\n", r ? r : "(null)");
+
+ return 0;
+}
diff --git a/src/mygetopt.h b/src/mygetopt.h
new file mode 100644
index 0000000..d766762
--- /dev/null
+++ b/src/mygetopt.h
@@ -0,0 +1,68 @@
+/* $NetBSD: getopt.h,v 1.8 2007/11/06 19:21:18 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _GETOPT_H_
+#define _GETOPT_H_
+
+#include <unistd.h>
+
+/*
+ * Gnu like getopt_long() and BSD4.4 getsubopt()/optreset extensions
+ */
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+struct option {
+ /* name of long option */
+ const char *name;
+ /*
+ * one of no_argument, required_argument, and optional_argument:
+ * whether option takes an argument
+ */
+ int has_arg;
+ /* if not NULL, set *flag to val when option found */
+ int *flag;
+ /* if flag not NULL, value to set *flag to; else return value */
+ int val;
+};
+
+int getopt_long(int, char * const *, const char *,
+ const struct option *, int *);
+
+#endif /* !_GETOPT_H_ */
diff --git a/src/pread.c b/src/pread.c
new file mode 100644
index 0000000..dfaad3b
--- /dev/null
+++ b/src/pread.c
@@ -0,0 +1,23 @@
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: pread.c,v 1.5 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+#include <fcntl.h>
+#include <unistd.h>
+
+ssize_t
+pread(int fd, void *buf, size_t len, off_t off) {
+ off_t old;
+ ssize_t rv;
+
+ if ((old = lseek(fd, off, SEEK_SET)) == -1)
+ return -1;
+
+ if ((rv = read(fd, buf, len)) == -1)
+ return -1;
+
+ if (lseek(fd, old, SEEK_SET) == -1)
+ return -1;
+
+ return rv;
+}
diff --git a/src/print.c b/src/print.c
new file mode 100644
index 0000000..18f42a0
--- /dev/null
+++ b/src/print.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * print.c - debugging printout routines
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: print.c,v 1.97 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <time.h>
+
+#include "cdf.h"
+
+#ifndef COMPILE_ONLY
+file_protected void
+file_mdump(struct magic *m)
+{
+ static const char optyp[] = { FILE_OPS };
+ char tbuf[256];
+
+ (void) fprintf(stderr, "%u: %.*s %d", m->lineno,
+ (m->cont_level & 7) + 1, ">>>>>>>>", m->offset);
+
+ if (m->flag & INDIR) {
+ (void) fprintf(stderr, "(%s,",
+ /* Note: type is unsigned */
+ (m->in_type < file_nnames) ? file_names[m->in_type] :
+ "*bad in_type*");
+ if (m->in_op & FILE_OPINVERSE)
+ (void) fputc('~', stderr);
+ (void) fprintf(stderr, "%c%d),",
+ (CAST(size_t, m->in_op & FILE_OPS_MASK) <
+ __arraycount(optyp)) ?
+ optyp[m->in_op & FILE_OPS_MASK] : '?', m->in_offset);
+ }
+ (void) fprintf(stderr, " %s%s", (m->flag & UNSIGNED) ? "u" : "",
+ /* Note: type is unsigned */
+ (m->type < file_nnames) ? file_names[m->type] : "*bad type");
+ if (m->mask_op & FILE_OPINVERSE)
+ (void) fputc('~', stderr);
+
+ if (IS_STRING(m->type)) {
+ if (m->str_flags) {
+ (void) fputc('/', stderr);
+ if (m->str_flags & STRING_COMPACT_WHITESPACE)
+ (void) fputc(CHAR_COMPACT_WHITESPACE, stderr);
+ if (m->str_flags & STRING_COMPACT_OPTIONAL_WHITESPACE)
+ (void) fputc(CHAR_COMPACT_OPTIONAL_WHITESPACE,
+ stderr);
+ if (m->str_flags & STRING_IGNORE_LOWERCASE)
+ (void) fputc(CHAR_IGNORE_LOWERCASE, stderr);
+ if (m->str_flags & STRING_IGNORE_UPPERCASE)
+ (void) fputc(CHAR_IGNORE_UPPERCASE, stderr);
+ if (m->str_flags & REGEX_OFFSET_START)
+ (void) fputc(CHAR_REGEX_OFFSET_START, stderr);
+ if (m->str_flags & STRING_TEXTTEST)
+ (void) fputc(CHAR_TEXTTEST, stderr);
+ if (m->str_flags & STRING_BINTEST)
+ (void) fputc(CHAR_BINTEST, stderr);
+ if (m->str_flags & PSTRING_1_BE)
+ (void) fputc(CHAR_PSTRING_1_BE, stderr);
+ if (m->str_flags & PSTRING_2_BE)
+ (void) fputc(CHAR_PSTRING_2_BE, stderr);
+ if (m->str_flags & PSTRING_2_LE)
+ (void) fputc(CHAR_PSTRING_2_LE, stderr);
+ if (m->str_flags & PSTRING_4_BE)
+ (void) fputc(CHAR_PSTRING_4_BE, stderr);
+ if (m->str_flags & PSTRING_4_LE)
+ (void) fputc(CHAR_PSTRING_4_LE, stderr);
+ if (m->str_flags & PSTRING_LENGTH_INCLUDES_ITSELF)
+ (void) fputc(
+ CHAR_PSTRING_LENGTH_INCLUDES_ITSELF,
+ stderr);
+ }
+ if (m->str_range)
+ (void) fprintf(stderr, "/%u", m->str_range);
+ }
+ else {
+ if (CAST(size_t, m->mask_op & FILE_OPS_MASK) <
+ __arraycount(optyp))
+ (void) fputc(optyp[m->mask_op & FILE_OPS_MASK], stderr);
+ else
+ (void) fputc('?', stderr);
+
+ if (m->num_mask) {
+ (void) fprintf(stderr, "%.8llx",
+ CAST(unsigned long long, m->num_mask));
+ }
+ }
+ (void) fprintf(stderr, ",%c", m->reln);
+
+ if (m->reln != 'x') {
+ switch (m->type) {
+ case FILE_BYTE:
+ case FILE_SHORT:
+ case FILE_LONG:
+ case FILE_LESHORT:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ case FILE_BESHORT:
+ case FILE_BELONG:
+ case FILE_INDIRECT:
+ (void) fprintf(stderr, "%d", CAST(int32_t, m->value.l));
+ break;
+ case FILE_BEQUAD:
+ case FILE_LEQUAD:
+ case FILE_QUAD:
+ case FILE_OFFSET:
+ (void) fprintf(stderr, "%" INT64_T_FORMAT "d",
+ CAST(long long, m->value.q));
+ break;
+ case FILE_PSTRING:
+ case FILE_STRING:
+ case FILE_REGEX:
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ case FILE_SEARCH:
+ file_showstr(stderr, m->value.s,
+ CAST(size_t, m->vallen));
+ break;
+ case FILE_DATE:
+ case FILE_LEDATE:
+ case FILE_BEDATE:
+ case FILE_MEDATE:
+ (void)fprintf(stderr, "%s,",
+ file_fmtdatetime(tbuf, sizeof(tbuf), m->value.l, 0));
+ break;
+ case FILE_LDATE:
+ case FILE_LELDATE:
+ case FILE_BELDATE:
+ case FILE_MELDATE:
+ (void)fprintf(stderr, "%s,",
+ file_fmtdatetime(tbuf, sizeof(tbuf), m->value.l,
+ FILE_T_LOCAL));
+ break;
+ case FILE_QDATE:
+ case FILE_LEQDATE:
+ case FILE_BEQDATE:
+ (void)fprintf(stderr, "%s,",
+ file_fmtdatetime(tbuf, sizeof(tbuf), m->value.q, 0));
+ break;
+ case FILE_QLDATE:
+ case FILE_LEQLDATE:
+ case FILE_BEQLDATE:
+ (void)fprintf(stderr, "%s,",
+ file_fmtdatetime(tbuf, sizeof(tbuf), m->value.q,
+ FILE_T_LOCAL));
+ break;
+ case FILE_QWDATE:
+ case FILE_LEQWDATE:
+ case FILE_BEQWDATE:
+ (void)fprintf(stderr, "%s,",
+ file_fmtdatetime(tbuf, sizeof(tbuf), m->value.q,
+ FILE_T_WINDOWS));
+ break;
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ (void) fprintf(stderr, "%G", m->value.f);
+ break;
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ (void) fprintf(stderr, "%G", m->value.d);
+ break;
+ case FILE_LEVARINT:
+ case FILE_BEVARINT:
+ (void)fprintf(stderr, "%s", file_fmtvarint(
+ tbuf, sizeof(tbuf), m->value.us, m->type));
+ break;
+ case FILE_MSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ (void)fprintf(stderr, "%s,",
+ file_fmtdate(tbuf, sizeof(tbuf), m->value.h));
+ break;
+ case FILE_MSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ (void)fprintf(stderr, "%s,",
+ file_fmttime(tbuf, sizeof(tbuf), m->value.h));
+ break;
+ case FILE_OCTAL:
+ (void)fprintf(stderr, "%s",
+ file_fmtnum(tbuf, sizeof(tbuf), m->value.s, 8));
+ break;
+ case FILE_DEFAULT:
+ /* XXX - do anything here? */
+ break;
+ case FILE_USE:
+ case FILE_NAME:
+ case FILE_DER:
+ (void) fprintf(stderr, "'%s'", m->value.s);
+ break;
+ case FILE_GUID:
+ (void) file_print_guid(tbuf, sizeof(tbuf),
+ m->value.guid);
+ (void) fprintf(stderr, "%s", tbuf);
+ break;
+
+ default:
+ (void) fprintf(stderr, "*bad type %d*", m->type);
+ break;
+ }
+ }
+ (void) fprintf(stderr, ",\"%s\"]\n", m->desc);
+}
+#endif
+
+/*VARARGS*/
+file_protected void
+file_magwarn(struct magic_set *ms, const char *f, ...)
+{
+ va_list va;
+
+ /* cuz we use stdout for most, stderr here */
+ (void) fflush(stdout);
+
+ if (ms && ms->file)
+ (void) fprintf(stderr, "%s, %lu: ", ms->file,
+ CAST(unsigned long, ms->line));
+ (void) fprintf(stderr, "Warning: ");
+ va_start(va, f);
+ (void) vfprintf(stderr, f, va);
+ va_end(va);
+ (void) fputc('\n', stderr);
+}
+
+file_protected const char *
+file_fmtvarint(char *buf, size_t blen, const unsigned char *us, int t)
+{
+ snprintf(buf, blen, "%jd", CAST(intmax_t,
+ file_varint2uintmax_t(us, t, NULL)));
+ return buf;
+}
+
+file_protected const char *
+file_fmtdatetime(char *buf, size_t bsize, uint64_t v, int flags)
+{
+ char *pp;
+ time_t t;
+ struct tm *tm, tmz;
+
+ if (flags & FILE_T_WINDOWS) {
+ struct timespec ts;
+ cdf_timestamp_to_timespec(&ts, CAST(cdf_timestamp_t, v));
+ t = ts.tv_sec;
+ } else {
+ // XXX: perhaps detect and print something if overflow
+ // on 32 bit time_t?
+ t = CAST(time_t, v);
+ }
+
+ if (flags & FILE_T_LOCAL) {
+ tm = localtime_r(&t, &tmz);
+ } else {
+ tm = gmtime_r(&t, &tmz);
+ }
+ if (tm == NULL)
+ goto out;
+ pp = asctime_r(tm, buf);
+
+ if (pp == NULL)
+ goto out;
+ pp[strcspn(pp, "\n")] = '\0';
+ return pp;
+out:
+ strlcpy(buf, "*Invalid datetime*", bsize);
+ return buf;
+}
+
+/*
+ * https://docs.microsoft.com/en-us/windows/win32/api/winbase/\
+ * nf-winbase-dosdatetimetofiletime?redirectedfrom=MSDN
+ */
+file_protected const char *
+file_fmtdate(char *buf, size_t bsize, uint16_t v)
+{
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_mday = v & 0x1f;
+ tm.tm_mon = ((v >> 5) & 0xf) - 1;
+ tm.tm_year = (v >> 9) + 80;
+
+ if (strftime(buf, bsize, "%a, %b %d %Y", &tm) == 0)
+ goto out;
+
+ return buf;
+out:
+ strlcpy(buf, "*Invalid date*", bsize);
+ return buf;
+}
+
+file_protected const char *
+file_fmttime(char *buf, size_t bsize, uint16_t v)
+{
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_sec = (v & 0x1f) * 2;
+ tm.tm_min = ((v >> 5) & 0x3f);
+ tm.tm_hour = (v >> 11);
+
+ if (strftime(buf, bsize, "%T", &tm) == 0)
+ goto out;
+
+ return buf;
+out:
+ strlcpy(buf, "*Invalid time*", bsize);
+ return buf;
+
+}
+
+file_protected const char *
+file_fmtnum(char *buf, size_t blen, const char *us, int base)
+{
+ char *endptr;
+ unsigned long long val;
+
+ errno = 0;
+ val = strtoull(us, &endptr, base);
+ if (*endptr || errno) {
+bad: strlcpy(buf, "*Invalid number*", blen);
+ return buf;
+ }
+
+ if (snprintf(buf, blen, "%llu", val) < 0)
+ goto bad;
+ return buf;
+}
diff --git a/src/readcdf.c b/src/readcdf.c
new file mode 100644
index 0000000..1e2593a
--- /dev/null
+++ b/src/readcdf.c
@@ -0,0 +1,682 @@
+/*-
+ * Copyright (c) 2008, 2016 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: readcdf.c,v 1.79 2022/12/26 17:31:14 christos Exp $")
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "cdf.h"
+#include "magic.h"
+
+#define NOTMIME(ms) (((ms)->flags & MAGIC_MIME) == 0)
+
+static const struct nv {
+ const char *pattern;
+ const char *mime;
+} app2mime[] = {
+ { "Word", "msword", },
+ { "Excel", "vnd.ms-excel", },
+ { "Powerpoint", "vnd.ms-powerpoint", },
+ { "Crystal Reports", "x-rpt", },
+ { "Advanced Installer", "vnd.ms-msi", },
+ { "InstallShield", "vnd.ms-msi", },
+ { "Microsoft Patch Compiler", "vnd.ms-msi", },
+ { "NAnt", "vnd.ms-msi", },
+ { "Windows Installer", "vnd.ms-msi", },
+ { NULL, NULL, },
+}, name2mime[] = {
+ { "Book", "vnd.ms-excel", },
+ { "Workbook", "vnd.ms-excel", },
+ { "WordDocument", "msword", },
+ { "PowerPoint", "vnd.ms-powerpoint", },
+ { "DigitalSignature", "vnd.ms-msi", },
+ { NULL, NULL, },
+}, name2desc[] = {
+ { "Book", "Microsoft Excel", },
+ { "Workbook", "Microsoft Excel", },
+ { "WordDocument", "Microsoft Word", },
+ { "PowerPoint", "Microsoft PowerPoint", },
+ { "DigitalSignature", "Microsoft Installer", },
+ { NULL, NULL, },
+};
+
+static const struct cv {
+ uint64_t clsid[2];
+ const char *mime;
+} clsid2mime[] = {
+ {
+ { 0x00000000000c1084ULL, 0x46000000000000c0ULL },
+ "x-msi",
+ },
+ { { 0, 0 },
+ NULL,
+ },
+}, clsid2desc[] = {
+ {
+ { 0x00000000000c1084ULL, 0x46000000000000c0ULL },
+ "MSI Installer",
+ },
+ { { 0, 0 },
+ NULL,
+ },
+};
+
+file_private const char *
+cdf_clsid_to_mime(const uint64_t clsid[2], const struct cv *cv)
+{
+ size_t i;
+ for (i = 0; cv[i].mime != NULL; i++) {
+ if (clsid[0] == cv[i].clsid[0] && clsid[1] == cv[i].clsid[1])
+ return cv[i].mime;
+ }
+#ifdef CDF_DEBUG
+ fprintf(stderr, "unknown mime %" PRIx64 ", %" PRIx64 "\n", clsid[0],
+ clsid[1]);
+#endif
+ return NULL;
+}
+
+file_private const char *
+cdf_app_to_mime(const char *vbuf, const struct nv *nv)
+{
+ size_t i;
+ const char *rv = NULL;
+#ifdef USE_C_LOCALE
+ locale_t old_lc_ctype, c_lc_ctype;
+
+ c_lc_ctype = newlocale(LC_CTYPE_MASK, "C", 0);
+ assert(c_lc_ctype != NULL);
+ old_lc_ctype = uselocale(c_lc_ctype);
+ assert(old_lc_ctype != NULL);
+#else
+ char *old_lc_ctype = setlocale(LC_CTYPE, NULL);
+ assert(old_lc_ctype != NULL);
+ old_lc_ctype = strdup(old_lc_ctype);
+ assert(old_lc_ctype != NULL);
+ (void)setlocale(LC_CTYPE, "C");
+#endif
+ for (i = 0; nv[i].pattern != NULL; i++)
+ if (strcasestr(vbuf, nv[i].pattern) != NULL) {
+ rv = nv[i].mime;
+ break;
+ }
+#ifdef CDF_DEBUG
+ fprintf(stderr, "unknown app %s\n", vbuf);
+#endif
+#ifdef USE_C_LOCALE
+ (void)uselocale(old_lc_ctype);
+ freelocale(c_lc_ctype);
+#else
+ (void)setlocale(LC_CTYPE, old_lc_ctype);
+ free(old_lc_ctype);
+#endif
+ return rv;
+}
+
+file_private int
+cdf_file_property_info(struct magic_set *ms, const cdf_property_info_t *info,
+ size_t count, const cdf_directory_t *root_storage)
+{
+ size_t i;
+ cdf_timestamp_t tp;
+ struct timespec ts;
+ char buf[64];
+ const char *str = NULL;
+ const char *s, *e;
+ int len;
+
+ if (!NOTMIME(ms) && root_storage)
+ str = cdf_clsid_to_mime(root_storage->d_storage_uuid,
+ clsid2mime);
+
+ for (i = 0; i < count; i++) {
+ cdf_print_property_name(buf, sizeof(buf), info[i].pi_id);
+ switch (info[i].pi_type) {
+ case CDF_NULL:
+ break;
+ case CDF_SIGNED16:
+ if (NOTMIME(ms) && file_printf(ms, ", %s: %hd", buf,
+ info[i].pi_s16) == -1)
+ return -1;
+ break;
+ case CDF_SIGNED32:
+ if (NOTMIME(ms) && file_printf(ms, ", %s: %d", buf,
+ info[i].pi_s32) == -1)
+ return -1;
+ break;
+ case CDF_UNSIGNED32:
+ if (NOTMIME(ms) && file_printf(ms, ", %s: %u", buf,
+ info[i].pi_u32) == -1)
+ return -1;
+ break;
+ case CDF_FLOAT:
+ if (NOTMIME(ms) && file_printf(ms, ", %s: %g", buf,
+ info[i].pi_f) == -1)
+ return -1;
+ break;
+ case CDF_DOUBLE:
+ if (NOTMIME(ms) && file_printf(ms, ", %s: %g", buf,
+ info[i].pi_d) == -1)
+ return -1;
+ break;
+ case CDF_LENGTH32_STRING:
+ case CDF_LENGTH32_WSTRING:
+ len = info[i].pi_str.s_len;
+ if (len > 1) {
+ char vbuf[1024];
+ size_t j, k = 1;
+
+ if (info[i].pi_type == CDF_LENGTH32_WSTRING)
+ k++;
+ s = info[i].pi_str.s_buf;
+ e = info[i].pi_str.s_buf + len;
+ for (j = 0; s < e && j < sizeof(vbuf)
+ && len--; s += k) {
+ if (*s == '\0')
+ break;
+ if (isprint(CAST(unsigned char, *s)))
+ vbuf[j++] = *s;
+ }
+ if (j == sizeof(vbuf))
+ --j;
+ vbuf[j] = '\0';
+ if (NOTMIME(ms)) {
+ if (vbuf[0]) {
+ if (file_printf(ms, ", %s: %s",
+ buf, vbuf) == -1)
+ return -1;
+ }
+ } else if (str == NULL && info[i].pi_id ==
+ CDF_PROPERTY_NAME_OF_APPLICATION) {
+ str = cdf_app_to_mime(vbuf, app2mime);
+ }
+ }
+ break;
+ case CDF_FILETIME:
+ tp = info[i].pi_tp;
+ if (tp != 0) {
+ char tbuf[64];
+ if (tp < 1000000000000000LL) {
+ cdf_print_elapsed_time(tbuf,
+ sizeof(tbuf), tp);
+ if (NOTMIME(ms) && file_printf(ms,
+ ", %s: %s", buf, tbuf) == -1)
+ return -1;
+ } else {
+ char *c, *ec;
+ cdf_timestamp_to_timespec(&ts, tp);
+ c = cdf_ctime(&ts.tv_sec, tbuf);
+ if (c != NULL &&
+ (ec = strchr(c, '\n')) != NULL)
+ *ec = '\0';
+
+ if (NOTMIME(ms) && file_printf(ms,
+ ", %s: %s", buf, c) == -1)
+ return -1;
+ }
+ }
+ break;
+ case CDF_CLIPBOARD:
+ break;
+ default:
+ return -1;
+ }
+ }
+ if (ms->flags & MAGIC_MIME_TYPE) {
+ if (str == NULL)
+ return 0;
+ if (file_printf(ms, "application/%s", str) == -1)
+ return -1;
+ }
+ return 1;
+}
+
+file_private int
+cdf_file_catalog(struct magic_set *ms, const cdf_header_t *h,
+ const cdf_stream_t *sst)
+{
+ cdf_catalog_t *cat;
+ size_t i;
+ char buf[256];
+ cdf_catalog_entry_t *ce;
+
+ if (NOTMIME(ms)) {
+ if (file_printf(ms, "Microsoft Thumbs.db [") == -1)
+ return -1;
+ if (cdf_unpack_catalog(h, sst, &cat) == -1)
+ return -1;
+ ce = cat->cat_e;
+ /* skip first entry since it has a , or paren */
+ for (i = 1; i < cat->cat_num; i++)
+ if (file_printf(ms, "%s%s",
+ cdf_u16tos8(buf, ce[i].ce_namlen, ce[i].ce_name),
+ i == cat->cat_num - 1 ? "]" : ", ") == -1) {
+ free(cat);
+ return -1;
+ }
+ free(cat);
+ } else if (ms->flags & MAGIC_MIME_TYPE) {
+ if (file_printf(ms, "application/CDFV2") == -1)
+ return -1;
+ }
+ return 1;
+}
+
+file_private int
+cdf_file_summary_info(struct magic_set *ms, const cdf_header_t *h,
+ const cdf_stream_t *sst, const cdf_directory_t *root_storage)
+{
+ cdf_summary_info_header_t si;
+ cdf_property_info_t *info;
+ size_t count;
+ int m;
+
+ if (cdf_unpack_summary_info(sst, h, &si, &info, &count) == -1)
+ return -1;
+
+ if (NOTMIME(ms)) {
+ const char *str;
+
+ if (file_printf(ms, "Composite Document File V2 Document")
+ == -1)
+ return -1;
+
+ if (file_printf(ms, ", %s Endian",
+ si.si_byte_order == 0xfffe ? "Little" : "Big") == -1)
+ return -2;
+ switch (si.si_os) {
+ case 2:
+ if (file_printf(ms, ", Os: Windows, Version %d.%d",
+ si.si_os_version & 0xff,
+ CAST(uint32_t, si.si_os_version) >> 8) == -1)
+ return -2;
+ break;
+ case 1:
+ if (file_printf(ms, ", Os: MacOS, Version %d.%d",
+ CAST(uint32_t, si.si_os_version) >> 8,
+ si.si_os_version & 0xff) == -1)
+ return -2;
+ break;
+ default:
+ if (file_printf(ms, ", Os %d, Version: %d.%d", si.si_os,
+ si.si_os_version & 0xff,
+ CAST(uint32_t, si.si_os_version) >> 8) == -1)
+ return -2;
+ break;
+ }
+ if (root_storage) {
+ str = cdf_clsid_to_mime(root_storage->d_storage_uuid,
+ clsid2desc);
+ if (str) {
+ if (file_printf(ms, ", %s", str) == -1)
+ return -2;
+ }
+ }
+ }
+
+ m = cdf_file_property_info(ms, info, count, root_storage);
+ free(info);
+
+ return m == -1 ? -2 : m;
+}
+
+#ifdef notdef
+file_private char *
+format_clsid(char *buf, size_t len, const uint64_t uuid[2]) {
+ snprintf(buf, len, "%.8" PRIx64 "-%.4" PRIx64 "-%.4" PRIx64 "-%.4"
+ PRIx64 "-%.12" PRIx64,
+ (uuid[0] >> 32) & (uint64_t)0x000000000ffffffffULL,
+ (uuid[0] >> 16) & (uint64_t)0x0000000000000ffffULL,
+ (uuid[0] >> 0) & (uint64_t)0x0000000000000ffffULL,
+ (uuid[1] >> 48) & (uint64_t)0x0000000000000ffffULL,
+ (uuid[1] >> 0) & (uint64_t)0x0000fffffffffffffULL);
+ return buf;
+}
+#endif
+
+file_private int
+cdf_file_catalog_info(struct magic_set *ms, const cdf_info_t *info,
+ const cdf_header_t *h, const cdf_sat_t *sat, const cdf_sat_t *ssat,
+ const cdf_stream_t *sst, const cdf_dir_t *dir, cdf_stream_t *scn)
+{
+ int i;
+
+ if ((i = cdf_read_user_stream(info, h, sat, ssat, sst,
+ dir, "Catalog", scn)) == -1)
+ return i;
+#ifdef CDF_DEBUG
+ cdf_dump_catalog(h, scn);
+#endif
+ if ((i = cdf_file_catalog(ms, h, scn)) == -1)
+ return -1;
+ return i;
+}
+
+file_private int
+cdf_check_summary_info(struct magic_set *ms, const cdf_info_t *info,
+ const cdf_header_t *h, const cdf_sat_t *sat, const cdf_sat_t *ssat,
+ const cdf_stream_t *sst, const cdf_dir_t *dir, cdf_stream_t *scn,
+ const cdf_directory_t *root_storage, const char **expn)
+{
+ int i;
+ const char *str = NULL;
+ cdf_directory_t *d;
+ char name[__arraycount(d->d_name)];
+ size_t j, k;
+
+#ifdef CDF_DEBUG
+ cdf_dump_summary_info(h, scn);
+#endif
+ if ((i = cdf_file_summary_info(ms, h, scn, root_storage)) < 0) {
+ *expn = "Can't expand summary_info";
+ return i;
+ }
+ if (i == 1)
+ return i;
+ for (j = 0; str == NULL && j < dir->dir_len; j++) {
+ d = &dir->dir_tab[j];
+ for (k = 0; k < sizeof(name); k++)
+ name[k] = CAST(char, cdf_tole2(d->d_name[k]));
+ str = cdf_app_to_mime(name,
+ NOTMIME(ms) ? name2desc : name2mime);
+ }
+ if (NOTMIME(ms)) {
+ if (str != NULL) {
+ if (file_printf(ms, "%s", str) == -1)
+ return -1;
+ i = 1;
+ }
+ } else if (ms->flags & MAGIC_MIME_TYPE) {
+ if (str == NULL)
+ str = "vnd.ms-office";
+ if (file_printf(ms, "application/%s", str) == -1)
+ return -1;
+ i = 1;
+ }
+ if (i <= 0) {
+ i = cdf_file_catalog_info(ms, info, h, sat, ssat, sst,
+ dir, scn);
+ }
+ return i;
+}
+
+file_private struct sinfo {
+ const char *name;
+ const char *mime;
+ const char *sections[5];
+ const int types[5];
+} sectioninfo[] = {
+ { "Encrypted", "encrypted",
+ {
+ "EncryptedPackage", "EncryptedSummary",
+ NULL, NULL, NULL,
+ },
+ {
+ CDF_DIR_TYPE_USER_STREAM,
+ CDF_DIR_TYPE_USER_STREAM,
+ 0, 0, 0,
+
+ },
+ },
+ { "QuickBooks", "quickbooks",
+ {
+#if 0
+ "TaxForms", "PDFTaxForms", "modulesInBackup",
+#endif
+ "mfbu_header", NULL, NULL, NULL, NULL,
+ },
+ {
+#if 0
+ CDF_DIR_TYPE_USER_STORAGE,
+ CDF_DIR_TYPE_USER_STORAGE,
+ CDF_DIR_TYPE_USER_STREAM,
+#endif
+ CDF_DIR_TYPE_USER_STREAM,
+ 0, 0, 0, 0
+ },
+ },
+ { "Microsoft Excel", "vnd.ms-excel",
+ {
+ "Book", "Workbook", NULL, NULL, NULL,
+ },
+ {
+ CDF_DIR_TYPE_USER_STREAM,
+ CDF_DIR_TYPE_USER_STREAM,
+ 0, 0, 0,
+ },
+ },
+ { "Microsoft Word", "msword",
+ {
+ "WordDocument", NULL, NULL, NULL, NULL,
+ },
+ {
+ CDF_DIR_TYPE_USER_STREAM,
+ 0, 0, 0, 0,
+ },
+ },
+ { "Microsoft PowerPoint", "vnd.ms-powerpoint",
+ {
+ "PowerPoint", NULL, NULL, NULL, NULL,
+ },
+ {
+ CDF_DIR_TYPE_USER_STREAM,
+ 0, 0, 0, 0,
+ },
+ },
+ { "Microsoft Outlook Message", "vnd.ms-outlook",
+ {
+ "__properties_version1.0",
+ "__recip_version1.0_#00000000",
+ NULL, NULL, NULL,
+ },
+ {
+ CDF_DIR_TYPE_USER_STREAM,
+ CDF_DIR_TYPE_USER_STORAGE,
+ 0, 0, 0,
+ },
+ },
+};
+
+file_private int
+cdf_file_dir_info(struct magic_set *ms, const cdf_dir_t *dir)
+{
+ size_t sd, j;
+
+ for (sd = 0; sd < __arraycount(sectioninfo); sd++) {
+ const struct sinfo *si = &sectioninfo[sd];
+ for (j = 0; si->sections[j]; j++) {
+ if (cdf_find_stream(dir, si->sections[j], si->types[j])
+ > 0)
+ break;
+#ifdef CDF_DEBUG
+ fprintf(stderr, "Can't read %s\n", si->sections[j]);
+#endif
+ }
+ if (si->sections[j] == NULL)
+ continue;
+ if (NOTMIME(ms)) {
+ if (file_printf(ms, "CDFV2 %s", si->name) == -1)
+ return -1;
+ } else if (ms->flags & MAGIC_MIME_TYPE) {
+ if (file_printf(ms, "application/%s", si->mime) == -1)
+ return -1;
+ }
+ return 1;
+ }
+ return -1;
+}
+
+file_protected int
+file_trycdf(struct magic_set *ms, const struct buffer *b)
+{
+ int fd = b->fd;
+ const unsigned char *buf = CAST(const unsigned char *, b->fbuf);
+ size_t nbytes = b->flen;
+ cdf_info_t info;
+ cdf_header_t h;
+ cdf_sat_t sat, ssat;
+ cdf_stream_t sst, scn;
+ cdf_dir_t dir;
+ int i;
+ const char *expn = "";
+ const cdf_directory_t *root_storage;
+
+ scn.sst_tab = NULL;
+ info.i_fd = fd;
+ info.i_buf = buf;
+ info.i_len = nbytes;
+ if (ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION))
+ return 0;
+ if (cdf_read_header(&info, &h) == -1)
+ return 0;
+#ifdef CDF_DEBUG
+ cdf_dump_header(&h);
+#endif
+
+ if ((i = cdf_read_sat(&info, &h, &sat)) == -1) {
+ expn = "Can't read SAT";
+ goto out0;
+ }
+#ifdef CDF_DEBUG
+ cdf_dump_sat("SAT", &sat, CDF_SEC_SIZE(&h));
+#endif
+
+ if ((i = cdf_read_ssat(&info, &h, &sat, &ssat)) == -1) {
+ expn = "Can't read SSAT";
+ goto out1;
+ }
+#ifdef CDF_DEBUG
+ cdf_dump_sat("SSAT", &ssat, CDF_SHORT_SEC_SIZE(&h));
+#endif
+
+ if ((i = cdf_read_dir(&info, &h, &sat, &dir)) == -1) {
+ expn = "Can't read directory";
+ goto out2;
+ }
+
+ if ((i = cdf_read_short_stream(&info, &h, &sat, &dir, &sst,
+ &root_storage)) == -1) {
+ expn = "Cannot read short stream";
+ goto out3;
+ }
+#ifdef CDF_DEBUG
+ cdf_dump_dir(&info, &h, &sat, &ssat, &sst, &dir);
+#endif
+#ifdef notdef
+ if (root_storage) {
+ if (NOTMIME(ms)) {
+ char clsbuf[128];
+ if (file_printf(ms, "CLSID %s, ",
+ format_clsid(clsbuf, sizeof(clsbuf),
+ root_storage->d_storage_uuid)) == -1)
+ return -1;
+ }
+ }
+#endif
+
+ if (cdf_read_user_stream(&info, &h, &sat, &ssat, &sst, &dir,
+ "FileHeader", &scn) != -1) {
+#define HWP5_SIGNATURE "HWP Document File"
+ if (scn.sst_len * scn.sst_ss >= sizeof(HWP5_SIGNATURE) - 1
+ && memcmp(scn.sst_tab, HWP5_SIGNATURE,
+ sizeof(HWP5_SIGNATURE) - 1) == 0) {
+ if (NOTMIME(ms)) {
+ if (file_printf(ms,
+ "Hangul (Korean) Word Processor File 5.x") == -1)
+ return -1;
+ } else if (ms->flags & MAGIC_MIME_TYPE) {
+ if (file_printf(ms, "application/x-hwp") == -1)
+ return -1;
+ }
+ i = 1;
+ goto out5;
+ } else {
+ cdf_zero_stream(&scn);
+ }
+ }
+
+ if ((i = cdf_read_summary_info(&info, &h, &sat, &ssat, &sst, &dir,
+ &scn)) == -1) {
+ if (errno != ESRCH) {
+ expn = "Cannot read summary info";
+ }
+ } else {
+ i = cdf_check_summary_info(ms, &info, &h,
+ &sat, &ssat, &sst, &dir, &scn, root_storage, &expn);
+ cdf_zero_stream(&scn);
+ }
+ if (i <= 0) {
+ if ((i = cdf_read_doc_summary_info(&info, &h, &sat, &ssat,
+ &sst, &dir, &scn)) == -1) {
+ if (errno != ESRCH) {
+ expn = "Cannot read summary info";
+ }
+ } else {
+ i = cdf_check_summary_info(ms, &info, &h, &sat, &ssat,
+ &sst, &dir, &scn, root_storage, &expn);
+ }
+ }
+ if (i <= 0) {
+ i = cdf_file_dir_info(ms, &dir);
+ if (i < 0)
+ expn = "Cannot read section info";
+ }
+out5:
+ cdf_zero_stream(&scn);
+ cdf_zero_stream(&sst);
+out3:
+ free(dir.dir_tab);
+out2:
+ free(ssat.sat_tab);
+out1:
+ free(sat.sat_tab);
+out0:
+ /* If we handled it already, return */
+ if (i != -1)
+ return i;
+ /* Provide a default handler */
+ if (NOTMIME(ms)) {
+ if (file_printf(ms,
+ "Composite Document File V2 Document") == -1)
+ return -1;
+ if (*expn)
+ if (file_printf(ms, ", %s", expn) == -1)
+ return -1;
+ } else if (ms->flags & MAGIC_MIME_TYPE) {
+ /* https://reposcope.com/mimetype/application/x-ole-storage */
+ if (file_printf(ms, "application/x-ole-storage") == -1)
+ return -1;
+ }
+ return 1;
+}
diff --git a/src/readelf.c b/src/readelf.c
new file mode 100644
index 0000000..97d1451
--- /dev/null
+++ b/src/readelf.c
@@ -0,0 +1,1902 @@
+/*
+ * Copyright (c) Christos Zoulas 2003.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: readelf.c,v 1.187 2022/12/26 17:31:14 christos Exp $")
+#endif
+
+#ifdef BUILTIN_ELF
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "readelf.h"
+#include "magic.h"
+
+#ifdef ELFCORE
+file_private int dophn_core(struct magic_set *, int, int, int, off_t, int, size_t,
+ off_t, int *, uint16_t *);
+#endif
+file_private int dophn_exec(struct magic_set *, int, int, int, off_t, int, size_t,
+ off_t, int, int *, uint16_t *);
+file_private int doshn(struct magic_set *, int, int, int, off_t, int, size_t,
+ off_t, int, int, int *, uint16_t *);
+file_private size_t donote(struct magic_set *, void *, size_t, size_t, int,
+ int, size_t, int *, uint16_t *, int, off_t, int, off_t);
+
+#define ELF_ALIGN(a) ((((a) + align - 1) / align) * align)
+
+#define isquote(c) (strchr("'\"`", (c)) != NULL)
+
+file_private uint16_t getu16(int, uint16_t);
+file_private uint32_t getu32(int, uint32_t);
+file_private uint64_t getu64(int, uint64_t);
+
+#define MAX_PHNUM 128
+#define MAX_SHNUM 32768
+#define MAX_SHSIZE (64 * 1024 * 1024)
+#define SIZE_UNKNOWN CAST(off_t, -1)
+
+file_private int
+toomany(struct magic_set *ms, const char *name, uint16_t num)
+{
+ if (ms->flags & MAGIC_MIME)
+ return 1;
+ if (file_printf(ms, ", too many %s (%u)", name, num) == -1)
+ return -1;
+ return 1;
+}
+
+file_private uint16_t
+getu16(int swap, uint16_t value)
+{
+ union {
+ uint16_t ui;
+ char c[2];
+ } retval, tmpval;
+
+ if (swap) {
+ tmpval.ui = value;
+
+ retval.c[0] = tmpval.c[1];
+ retval.c[1] = tmpval.c[0];
+
+ return retval.ui;
+ } else
+ return value;
+}
+
+file_private uint32_t
+getu32(int swap, uint32_t value)
+{
+ union {
+ uint32_t ui;
+ char c[4];
+ } retval, tmpval;
+
+ if (swap) {
+ tmpval.ui = value;
+
+ retval.c[0] = tmpval.c[3];
+ retval.c[1] = tmpval.c[2];
+ retval.c[2] = tmpval.c[1];
+ retval.c[3] = tmpval.c[0];
+
+ return retval.ui;
+ } else
+ return value;
+}
+
+file_private uint64_t
+getu64(int swap, uint64_t value)
+{
+ union {
+ uint64_t ui;
+ char c[8];
+ } retval, tmpval;
+
+ if (swap) {
+ tmpval.ui = value;
+
+ retval.c[0] = tmpval.c[7];
+ retval.c[1] = tmpval.c[6];
+ retval.c[2] = tmpval.c[5];
+ retval.c[3] = tmpval.c[4];
+ retval.c[4] = tmpval.c[3];
+ retval.c[5] = tmpval.c[2];
+ retval.c[6] = tmpval.c[1];
+ retval.c[7] = tmpval.c[0];
+
+ return retval.ui;
+ } else
+ return value;
+}
+
+#define elf_getu16(swap, value) getu16(swap, value)
+#define elf_getu32(swap, value) getu32(swap, value)
+#define elf_getu64(swap, value) getu64(swap, value)
+
+#define xsh_addr (clazz == ELFCLASS32 \
+ ? CAST(void *, &sh32) \
+ : CAST(void *, &sh64))
+#define xsh_sizeof (clazz == ELFCLASS32 \
+ ? sizeof(sh32) \
+ : sizeof(sh64))
+#define xsh_size CAST(size_t, (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, sh32.sh_size) \
+ : elf_getu64(swap, sh64.sh_size)))
+#define xsh_offset CAST(off_t, (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, sh32.sh_offset) \
+ : elf_getu64(swap, sh64.sh_offset)))
+#define xsh_type (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, sh32.sh_type) \
+ : elf_getu32(swap, sh64.sh_type))
+#define xsh_name (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, sh32.sh_name) \
+ : elf_getu32(swap, sh64.sh_name))
+
+#define xph_addr (clazz == ELFCLASS32 \
+ ? CAST(void *, &ph32) \
+ : CAST(void *, &ph64))
+#define xph_sizeof (clazz == ELFCLASS32 \
+ ? sizeof(ph32) \
+ : sizeof(ph64))
+#define xph_type (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, ph32.p_type) \
+ : elf_getu32(swap, ph64.p_type))
+#define xph_offset CAST(off_t, (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, ph32.p_offset) \
+ : elf_getu64(swap, ph64.p_offset)))
+#define xph_align CAST(size_t, (clazz == ELFCLASS32 \
+ ? CAST(off_t, (ph32.p_align ? \
+ elf_getu32(swap, ph32.p_align) : 4))\
+ : CAST(off_t, (ph64.p_align ? \
+ elf_getu64(swap, ph64.p_align) : 4))))
+#define xph_vaddr CAST(size_t, (clazz == ELFCLASS32 \
+ ? CAST(off_t, (ph32.p_vaddr ? \
+ elf_getu32(swap, ph32.p_vaddr) : 4))\
+ : CAST(off_t, (ph64.p_vaddr ? \
+ elf_getu64(swap, ph64.p_vaddr) : 4))))
+#define xph_filesz CAST(size_t, (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, ph32.p_filesz) \
+ : elf_getu64(swap, ph64.p_filesz)))
+#define xph_memsz CAST(size_t, ((clazz == ELFCLASS32 \
+ ? elf_getu32(swap, ph32.p_memsz) \
+ : elf_getu64(swap, ph64.p_memsz))))
+#define xnh_addr (clazz == ELFCLASS32 \
+ ? CAST(void *, &nh32) \
+ : CAST(void *, &nh64))
+#define xnh_sizeof (clazz == ELFCLASS32 \
+ ? sizeof(nh32) \
+ : sizeof(nh64))
+#define xnh_type (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, nh32.n_type) \
+ : elf_getu32(swap, nh64.n_type))
+#define xnh_namesz (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, nh32.n_namesz) \
+ : elf_getu32(swap, nh64.n_namesz))
+#define xnh_descsz (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, nh32.n_descsz) \
+ : elf_getu32(swap, nh64.n_descsz))
+
+#define xdh_addr (clazz == ELFCLASS32 \
+ ? CAST(void *, &dh32) \
+ : CAST(void *, &dh64))
+#define xdh_sizeof (clazz == ELFCLASS32 \
+ ? sizeof(dh32) \
+ : sizeof(dh64))
+#define xdh_tag (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, dh32.d_tag) \
+ : elf_getu64(swap, dh64.d_tag))
+#define xdh_val (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, dh32.d_un.d_val) \
+ : elf_getu64(swap, dh64.d_un.d_val))
+
+#define xcap_addr (clazz == ELFCLASS32 \
+ ? CAST(void *, &cap32) \
+ : CAST(void *, &cap64))
+#define xcap_sizeof (clazz == ELFCLASS32 \
+ ? sizeof(cap32) \
+ : sizeof(cap64))
+#define xcap_tag (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, cap32.c_tag) \
+ : elf_getu64(swap, cap64.c_tag))
+#define xcap_val (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, cap32.c_un.c_val) \
+ : elf_getu64(swap, cap64.c_un.c_val))
+
+#define xauxv_addr (clazz == ELFCLASS32 \
+ ? CAST(void *, &auxv32) \
+ : CAST(void *, &auxv64))
+#define xauxv_sizeof (clazz == ELFCLASS32 \
+ ? sizeof(auxv32) \
+ : sizeof(auxv64))
+#define xauxv_type (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, auxv32.a_type) \
+ : elf_getu64(swap, auxv64.a_type))
+#define xauxv_val (clazz == ELFCLASS32 \
+ ? elf_getu32(swap, auxv32.a_v) \
+ : elf_getu64(swap, auxv64.a_v))
+
+#define prpsoffsets(i) (clazz == ELFCLASS32 \
+ ? prpsoffsets32[i] \
+ : prpsoffsets64[i])
+
+#ifdef ELFCORE
+/*
+ * Try larger offsets first to avoid false matches
+ * from earlier data that happen to look like strings.
+ */
+static const size_t prpsoffsets32[] = {
+#ifdef USE_NT_PSINFO
+ 104, /* SunOS 5.x (command line) */
+ 88, /* SunOS 5.x (short name) */
+#endif /* USE_NT_PSINFO */
+
+ 100, /* SunOS 5.x (command line) */
+ 84, /* SunOS 5.x (short name) */
+
+ 44, /* Linux (command line) */
+ 28, /* Linux (short name) */
+
+ 48, /* Linux PowerPC (command line) */
+ 32, /* Linux PowerPC (short name) */
+
+ 8, /* FreeBSD */
+};
+
+static const size_t prpsoffsets64[] = {
+#ifdef USE_NT_PSINFO
+ 152, /* SunOS 5.x (command line) */
+ 136, /* SunOS 5.x (short name) */
+#endif /* USE_NT_PSINFO */
+
+ 136, /* SunOS 5.x, 64-bit (command line) */
+ 120, /* SunOS 5.x, 64-bit (short name) */
+
+ 56, /* Linux (command line) */
+ 40, /* Linux (tested on core from 2.4.x, short name) */
+
+ 16, /* FreeBSD, 64-bit */
+};
+
+#define NOFFSETS32 __arraycount(prpsoffsets32)
+#define NOFFSETS64 __arraycount(prpsoffsets64)
+
+#define NOFFSETS (clazz == ELFCLASS32 ? NOFFSETS32 : NOFFSETS64)
+
+/*
+ * Look through the program headers of an executable image, searching
+ * for a PT_NOTE section of type NT_PRPSINFO, with a name "CORE" or
+ * "FreeBSD"; if one is found, try looking in various places in its
+ * contents for a 16-character string containing only printable
+ * characters - if found, that string should be the name of the program
+ * that dropped core. Note: right after that 16-character string is,
+ * at least in SunOS 5.x (and possibly other SVR4-flavored systems) and
+ * Linux, a longer string (80 characters, in 5.x, probably other
+ * SVR4-flavored systems, and Linux) containing the start of the
+ * command line for that program.
+ *
+ * SunOS 5.x core files contain two PT_NOTE sections, with the types
+ * NT_PRPSINFO (old) and NT_PSINFO (new). These structs contain the
+ * same info about the command name and command line, so it probably
+ * isn't worthwhile to look for NT_PSINFO, but the offsets are provided
+ * above (see USE_NT_PSINFO), in case we ever decide to do so. The
+ * NT_PRPSINFO and NT_PSINFO sections are always in order and adjacent;
+ * the SunOS 5.x file command relies on this (and prefers the latter).
+ *
+ * The signal number probably appears in a section of type NT_PRSTATUS,
+ * but that's also rather OS-dependent, in ways that are harder to
+ * dissect with heuristics, so I'm not bothering with the signal number.
+ * (I suppose the signal number could be of interest in situations where
+ * you don't have the binary of the program that dropped core; if you
+ * *do* have that binary, the debugger will probably tell you what
+ * signal it was.)
+ */
+
+#define OS_STYLE_SVR4 0
+#define OS_STYLE_FREEBSD 1
+#define OS_STYLE_NETBSD 2
+
+file_private const char os_style_names[][8] = {
+ "SVR4",
+ "FreeBSD",
+ "NetBSD",
+};
+
+#define FLAGS_CORE_STYLE 0x0003
+
+#define FLAGS_DID_CORE 0x0004
+#define FLAGS_DID_OS_NOTE 0x0008
+#define FLAGS_DID_BUILD_ID 0x0010
+#define FLAGS_DID_CORE_STYLE 0x0020
+#define FLAGS_DID_NETBSD_PAX 0x0040
+#define FLAGS_DID_NETBSD_MARCH 0x0080
+#define FLAGS_DID_NETBSD_CMODEL 0x0100
+#define FLAGS_DID_NETBSD_EMULATION 0x0200
+#define FLAGS_DID_NETBSD_UNKNOWN 0x0400
+#define FLAGS_IS_CORE 0x0800
+#define FLAGS_DID_AUXV 0x1000
+
+file_private int
+dophn_core(struct magic_set *ms, int clazz, int swap, int fd, off_t off,
+ int num, size_t size, off_t fsize, int *flags, uint16_t *notecount)
+{
+ Elf32_Phdr ph32;
+ Elf64_Phdr ph64;
+ size_t offset, len;
+ unsigned char nbuf[BUFSIZ];
+ ssize_t bufsize;
+ off_t ph_off = off, offs;
+ int ph_num = num;
+
+ if (ms->flags & MAGIC_MIME)
+ return 0;
+
+ if (num == 0) {
+ if (file_printf(ms, ", no program header") == -1)
+ return -1;
+ return 0;
+ }
+ if (size != xph_sizeof) {
+ if (file_printf(ms, ", corrupted program header size") == -1)
+ return -1;
+ return 0;
+ }
+
+ /*
+ * Loop through all the program headers.
+ */
+ for ( ; num; num--) {
+ if (pread(fd, xph_addr, xph_sizeof, off) <
+ CAST(ssize_t, xph_sizeof)) {
+ if (file_printf(ms,
+ ", can't read elf program headers at %jd",
+ (intmax_t)off) == -1)
+ return -1;
+ return 0;
+ }
+ off += size;
+
+ if (fsize != SIZE_UNKNOWN && xph_offset > fsize) {
+ /* Perhaps warn here */
+ continue;
+ }
+
+ if (xph_type != PT_NOTE)
+ continue;
+
+ /*
+ * This is a PT_NOTE section; loop through all the notes
+ * in the section.
+ */
+ len = xph_filesz < sizeof(nbuf) ? xph_filesz : sizeof(nbuf);
+ offs = xph_offset;
+ if ((bufsize = pread(fd, nbuf, len, offs)) == -1) {
+ if (file_printf(ms, " can't read note section at %jd",
+ (intmax_t)offs) == -1)
+ return -1;
+ return 0;
+ }
+ offset = 0;
+ for (;;) {
+ if (offset >= CAST(size_t, bufsize))
+ break;
+ offset = donote(ms, nbuf, offset, CAST(size_t, bufsize),
+ clazz, swap, 4, flags, notecount, fd, ph_off,
+ ph_num, fsize);
+ if (offset == 0)
+ break;
+
+ }
+ }
+ return 0;
+}
+#endif
+
+static int
+do_note_netbsd_version(struct magic_set *ms, int swap, void *v)
+{
+ uint32_t desc;
+ memcpy(&desc, v, sizeof(desc));
+ desc = elf_getu32(swap, desc);
+
+ if (file_printf(ms, ", for NetBSD") == -1)
+ return -1;
+ /*
+ * The version number used to be stuck as 199905, and was thus
+ * basically content-free. Newer versions of NetBSD have fixed
+ * this and now use the encoding of __NetBSD_Version__:
+ *
+ * MMmmrrpp00
+ *
+ * M = major version
+ * m = minor version
+ * r = release ["",A-Z,Z[A-Z] but numeric]
+ * p = patchlevel
+ */
+ if (desc > 100000000U) {
+ uint32_t ver_patch = (desc / 100) % 100;
+ uint32_t ver_rel = (desc / 10000) % 100;
+ uint32_t ver_min = (desc / 1000000) % 100;
+ uint32_t ver_maj = desc / 100000000;
+
+ if (file_printf(ms, " %u.%u", ver_maj, ver_min) == -1)
+ return -1;
+ if (ver_maj >= 9) {
+ ver_patch += 100 * ver_rel;
+ ver_rel = 0;
+ }
+ if (ver_rel == 0 && ver_patch != 0) {
+ if (file_printf(ms, ".%u", ver_patch) == -1)
+ return -1;
+ } else if (ver_rel != 0) {
+ while (ver_rel > 26) {
+ if (file_printf(ms, "Z") == -1)
+ return -1;
+ ver_rel -= 26;
+ }
+ if (file_printf(ms, "%c", 'A' + ver_rel - 1) == -1)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+do_note_freebsd_version(struct magic_set *ms, int swap, void *v)
+{
+ uint32_t desc;
+
+ memcpy(&desc, v, sizeof(desc));
+ desc = elf_getu32(swap, desc);
+ if (file_printf(ms, ", for FreeBSD") == -1)
+ return -1;
+
+ /*
+ * Contents is __FreeBSD_version, whose relation to OS
+ * versions is defined by a huge table in the Porter's
+ * Handbook. This is the general scheme:
+ *
+ * Releases:
+ * Mmp000 (before 4.10)
+ * Mmi0p0 (before 5.0)
+ * Mmm0p0
+ *
+ * Development branches:
+ * Mmpxxx (before 4.6)
+ * Mmp1xx (before 4.10)
+ * Mmi1xx (before 5.0)
+ * M000xx (pre-M.0)
+ * Mmm1xx
+ *
+ * M = major version
+ * m = minor version
+ * i = minor version increment (491000 -> 4.10)
+ * p = patchlevel
+ * x = revision
+ *
+ * The first release of FreeBSD to use ELF by default
+ * was version 3.0.
+ */
+ if (desc == 460002) {
+ if (file_printf(ms, " 4.6.2") == -1)
+ return -1;
+ } else if (desc < 460100) {
+ if (file_printf(ms, " %d.%d", desc / 100000,
+ desc / 10000 % 10) == -1)
+ return -1;
+ if (desc / 1000 % 10 > 0)
+ if (file_printf(ms, ".%d", desc / 1000 % 10) == -1)
+ return -1;
+ if ((desc % 1000 > 0) || (desc % 100000 == 0))
+ if (file_printf(ms, " (%d)", desc) == -1)
+ return -1;
+ } else if (desc < 500000) {
+ if (file_printf(ms, " %d.%d", desc / 100000,
+ desc / 10000 % 10 + desc / 1000 % 10) == -1)
+ return -1;
+ if (desc / 100 % 10 > 0) {
+ if (file_printf(ms, " (%d)", desc) == -1)
+ return -1;
+ } else if (desc / 10 % 10 > 0) {
+ if (file_printf(ms, ".%d", desc / 10 % 10) == -1)
+ return -1;
+ }
+ } else {
+ if (file_printf(ms, " %d.%d", desc / 100000,
+ desc / 1000 % 100) == -1)
+ return -1;
+ if ((desc / 100 % 10 > 0) ||
+ (desc % 100000 / 100 == 0)) {
+ if (file_printf(ms, " (%d)", desc) == -1)
+ return -1;
+ } else if (desc / 10 % 10 > 0) {
+ if (file_printf(ms, ".%d", desc / 10 % 10) == -1)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+file_private int
+/*ARGSUSED*/
+do_bid_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
+ int swap __attribute__((__unused__)), uint32_t namesz, uint32_t descsz,
+ size_t noff, size_t doff, int *flags)
+{
+ if (namesz == 4 && strcmp(RCAST(char *, &nbuf[noff]), "GNU") == 0 &&
+ type == NT_GNU_BUILD_ID && (descsz >= 4 && descsz <= 20)) {
+ uint8_t desc[20];
+ const char *btype;
+ uint32_t i;
+ *flags |= FLAGS_DID_BUILD_ID;
+ switch (descsz) {
+ case 8:
+ btype = "xxHash";
+ break;
+ case 16:
+ btype = "md5/uuid";
+ break;
+ case 20:
+ btype = "sha1";
+ break;
+ default:
+ btype = "unknown";
+ break;
+ }
+ if (file_printf(ms, ", BuildID[%s]=", btype) == -1)
+ return -1;
+ memcpy(desc, &nbuf[doff], descsz);
+ for (i = 0; i < descsz; i++)
+ if (file_printf(ms, "%02x", desc[i]) == -1)
+ return -1;
+ return 1;
+ }
+ if (namesz == 4 && strcmp(RCAST(char *, &nbuf[noff]), "Go") == 0 &&
+ type == NT_GO_BUILD_ID && descsz < 128) {
+ char buf[256];
+ if (file_printf(ms, ", Go BuildID=%s",
+ file_copystr(buf, sizeof(buf), descsz,
+ RCAST(const char *, &nbuf[doff]))) == -1)
+ return -1;
+ return 1;
+ }
+ return 0;
+}
+
+file_private int
+do_os_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
+ int swap, uint32_t namesz, uint32_t descsz,
+ size_t noff, size_t doff, int *flags)
+{
+ const char *name = RCAST(const char *, &nbuf[noff]);
+
+ if (namesz == 5 && strcmp(name, "SuSE") == 0 &&
+ type == NT_GNU_VERSION && descsz == 2) {
+ *flags |= FLAGS_DID_OS_NOTE;
+ if (file_printf(ms, ", for SuSE %d.%d", nbuf[doff],
+ nbuf[doff + 1]) == -1)
+ return -1;
+ return 1;
+ }
+
+ if (namesz == 4 && strcmp(name, "GNU") == 0 &&
+ type == NT_GNU_VERSION && descsz == 16) {
+ uint32_t desc[4];
+ memcpy(desc, &nbuf[doff], sizeof(desc));
+
+ *flags |= FLAGS_DID_OS_NOTE;
+ if (file_printf(ms, ", for GNU/") == -1)
+ return -1;
+ switch (elf_getu32(swap, desc[0])) {
+ case GNU_OS_LINUX:
+ if (file_printf(ms, "Linux") == -1)
+ return -1;
+ break;
+ case GNU_OS_HURD:
+ if (file_printf(ms, "Hurd") == -1)
+ return -1;
+ break;
+ case GNU_OS_SOLARIS:
+ if (file_printf(ms, "Solaris") == -1)
+ return -1;
+ break;
+ case GNU_OS_KFREEBSD:
+ if (file_printf(ms, "kFreeBSD") == -1)
+ return -1;
+ break;
+ case GNU_OS_KNETBSD:
+ if (file_printf(ms, "kNetBSD") == -1)
+ return -1;
+ break;
+ default:
+ if (file_printf(ms, "<unknown>") == -1)
+ return -1;
+ }
+ if (file_printf(ms, " %d.%d.%d", elf_getu32(swap, desc[1]),
+ elf_getu32(swap, desc[2]), elf_getu32(swap, desc[3])) == -1)
+ return -1;
+ return 1;
+ }
+
+ if (namesz == 7 && strcmp(name, "NetBSD") == 0) {
+ if (type == NT_NETBSD_VERSION && descsz == 4) {
+ *flags |= FLAGS_DID_OS_NOTE;
+ if (do_note_netbsd_version(ms, swap, &nbuf[doff]) == -1)
+ return -1;
+ return 1;
+ }
+ }
+
+ if (namesz == 8 && strcmp(name, "FreeBSD") == 0) {
+ if (type == NT_FREEBSD_VERSION && descsz == 4) {
+ *flags |= FLAGS_DID_OS_NOTE;
+ if (do_note_freebsd_version(ms, swap, &nbuf[doff])
+ == -1)
+ return -1;
+ return 1;
+ }
+ }
+
+ if (namesz == 8 && strcmp(name, "OpenBSD") == 0 &&
+ type == NT_OPENBSD_VERSION && descsz == 4) {
+ *flags |= FLAGS_DID_OS_NOTE;
+ if (file_printf(ms, ", for OpenBSD") == -1)
+ return -1;
+ /* Content of note is always 0 */
+ return 1;
+ }
+
+ if (namesz == 10 && strcmp(name, "DragonFly") == 0 &&
+ type == NT_DRAGONFLY_VERSION && descsz == 4) {
+ uint32_t desc;
+ *flags |= FLAGS_DID_OS_NOTE;
+ if (file_printf(ms, ", for DragonFly") == -1)
+ return -1;
+ memcpy(&desc, &nbuf[doff], sizeof(desc));
+ desc = elf_getu32(swap, desc);
+ if (file_printf(ms, " %d.%d.%d", desc / 100000,
+ desc / 10000 % 10, desc % 10000) == -1)
+ return -1;
+ return 1;
+ }
+ return 0;
+}
+
+file_private int
+do_pax_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
+ int swap, uint32_t namesz, uint32_t descsz,
+ size_t noff, size_t doff, int *flags)
+{
+ const char *name = RCAST(const char *, &nbuf[noff]);
+
+ if (namesz == 4 && strcmp(name, "PaX") == 0 &&
+ type == NT_NETBSD_PAX && descsz == 4) {
+ static const char *pax[] = {
+ "+mprotect",
+ "-mprotect",
+ "+segvguard",
+ "-segvguard",
+ "+ASLR",
+ "-ASLR",
+ };
+ uint32_t desc;
+ size_t i;
+ int did = 0;
+
+ *flags |= FLAGS_DID_NETBSD_PAX;
+ memcpy(&desc, &nbuf[doff], sizeof(desc));
+ desc = elf_getu32(swap, desc);
+
+ if (desc && file_printf(ms, ", PaX: ") == -1)
+ return -1;
+
+ for (i = 0; i < __arraycount(pax); i++) {
+ if (((1 << CAST(int, i)) & desc) == 0)
+ continue;
+ if (file_printf(ms, "%s%s", did++ ? "," : "",
+ pax[i]) == -1)
+ return -1;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+file_private int
+do_core_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
+ int swap, uint32_t namesz, uint32_t descsz,
+ size_t noff, size_t doff, int *flags, size_t size, int clazz)
+{
+#ifdef ELFCORE
+ char buf[256];
+ const char *name = RCAST(const char *, &nbuf[noff]);
+
+ int os_style = -1;
+ /*
+ * Sigh. The 2.0.36 kernel in Debian 2.1, at
+ * least, doesn't correctly implement name
+ * sections, in core dumps, as specified by
+ * the "Program Linking" section of "UNIX(R) System
+ * V Release 4 Programmer's Guide: ANSI C and
+ * Programming Support Tools", because my copy
+ * clearly says "The first 'namesz' bytes in 'name'
+ * contain a *null-terminated* [emphasis mine]
+ * character representation of the entry's owner
+ * or originator", but the 2.0.36 kernel code
+ * doesn't include the terminating null in the
+ * name....
+ */
+ if ((namesz == 4 && strncmp(name, "CORE", 4) == 0) ||
+ (namesz == 5 && strcmp(name, "CORE") == 0)) {
+ os_style = OS_STYLE_SVR4;
+ }
+
+ if ((namesz == 8 && strcmp(name, "FreeBSD") == 0)) {
+ os_style = OS_STYLE_FREEBSD;
+ }
+
+ if ((namesz >= 11 && strncmp(name, "NetBSD-CORE", 11)
+ == 0)) {
+ os_style = OS_STYLE_NETBSD;
+ }
+
+ if (os_style != -1 && (*flags & FLAGS_DID_CORE_STYLE) == 0) {
+ if (file_printf(ms, ", %s-style", os_style_names[os_style])
+ == -1)
+ return -1;
+ *flags |= FLAGS_DID_CORE_STYLE;
+ *flags |= os_style;
+ }
+
+ switch (os_style) {
+ case OS_STYLE_NETBSD:
+ if (type == NT_NETBSD_CORE_PROCINFO) {
+ char sbuf[512];
+ struct NetBSD_elfcore_procinfo pi;
+ memset(&pi, 0, sizeof(pi));
+ memcpy(&pi, nbuf + doff, MIN(descsz, sizeof(pi)));
+
+ if (file_printf(ms, ", from '%.31s', pid=%u, uid=%u, "
+ "gid=%u, nlwps=%u, lwp=%u (signal %u/code %u)",
+ file_printable(ms, sbuf, sizeof(sbuf),
+ RCAST(char *, pi.cpi_name), sizeof(pi.cpi_name)),
+ elf_getu32(swap, CAST(uint32_t, pi.cpi_pid)),
+ elf_getu32(swap, pi.cpi_euid),
+ elf_getu32(swap, pi.cpi_egid),
+ elf_getu32(swap, pi.cpi_nlwps),
+ elf_getu32(swap, CAST(uint32_t, pi.cpi_siglwp)),
+ elf_getu32(swap, pi.cpi_signo),
+ elf_getu32(swap, pi.cpi_sigcode)) == -1)
+ return -1;
+
+ *flags |= FLAGS_DID_CORE;
+ return 1;
+ }
+ break;
+
+ case OS_STYLE_FREEBSD:
+ if (type == NT_PRPSINFO && *flags & FLAGS_IS_CORE) {
+ size_t argoff, pidoff;
+
+ if (clazz == ELFCLASS32)
+ argoff = 4 + 4 + 17;
+ else
+ argoff = 4 + 4 + 8 + 17;
+ if (file_printf(ms, ", from '%.80s'", nbuf + doff +
+ argoff) == -1)
+ return -1;
+ pidoff = argoff + 81 + 2;
+ if (doff + pidoff + 4 <= size) {
+ if (file_printf(ms, ", pid=%u",
+ elf_getu32(swap, *RCAST(uint32_t *, (nbuf +
+ doff + pidoff)))) == -1)
+ return -1;
+ }
+ *flags |= FLAGS_DID_CORE;
+ }
+ break;
+
+ default:
+ if (type == NT_PRPSINFO && *flags & FLAGS_IS_CORE) {
+ size_t i, j;
+ unsigned char c;
+ /*
+ * Extract the program name. We assume
+ * it to be 16 characters (that's what it
+ * is in SunOS 5.x and Linux).
+ *
+ * Unfortunately, it's at a different offset
+ * in various OSes, so try multiple offsets.
+ * If the characters aren't all printable,
+ * reject it.
+ */
+ for (i = 0; i < NOFFSETS; i++) {
+ unsigned char *cname, *cp;
+ size_t reloffset = prpsoffsets(i);
+ size_t noffset = doff + reloffset;
+ size_t k;
+ for (j = 0; j < 16; j++, noffset++,
+ reloffset++) {
+ /*
+ * Make sure we're not past
+ * the end of the buffer; if
+ * we are, just give up.
+ */
+ if (noffset >= size)
+ goto tryanother;
+
+ /*
+ * Make sure we're not past
+ * the end of the contents;
+ * if we are, this obviously
+ * isn't the right offset.
+ */
+ if (reloffset >= descsz)
+ goto tryanother;
+
+ c = nbuf[noffset];
+ if (c == '\0') {
+ /*
+ * A '\0' at the
+ * beginning is
+ * obviously wrong.
+ * Any other '\0'
+ * means we're done.
+ */
+ if (j == 0)
+ goto tryanother;
+ else
+ break;
+ } else {
+ /*
+ * A nonprintable
+ * character is also
+ * wrong.
+ */
+ if (!isprint(c) || isquote(c))
+ goto tryanother;
+ }
+ }
+ /*
+ * Well, that worked.
+ */
+
+ /*
+ * Try next offsets, in case this match is
+ * in the middle of a string.
+ */
+ for (k = i + 1 ; k < NOFFSETS; k++) {
+ size_t no;
+ int adjust = 1;
+ if (prpsoffsets(k) >= prpsoffsets(i))
+ continue;
+ /*
+ * pr_fname == pr_psargs - 16 &&
+ * non-nul-terminated fname (qemu)
+ */
+ if (prpsoffsets(k) ==
+ prpsoffsets(i) - 16 && j == 16)
+ continue;
+ for (no = doff + prpsoffsets(k);
+ no < doff + prpsoffsets(i); no++)
+ adjust = adjust
+ && isprint(nbuf[no]);
+ if (adjust)
+ i = k;
+ }
+
+ cname = CAST(unsigned char *,
+ &nbuf[doff + prpsoffsets(i)]);
+ for (cp = cname; cp < nbuf + size && *cp
+ && isprint(*cp); cp++)
+ continue;
+ /*
+ * Linux apparently appends a space at the end
+ * of the command line: remove it.
+ */
+ while (cp > cname && isspace(cp[-1]))
+ cp--;
+ if (file_printf(ms, ", from '%s'",
+ file_copystr(buf, sizeof(buf),
+ CAST(size_t, cp - cname),
+ RCAST(char *, cname))) == -1)
+ return -1;
+ *flags |= FLAGS_DID_CORE;
+ return 1;
+
+ tryanother:
+ ;
+ }
+ }
+ break;
+ }
+#endif
+ return 0;
+}
+
+file_private off_t
+get_offset_from_virtaddr(struct magic_set *ms, int swap, int clazz, int fd,
+ off_t off, int num, off_t fsize, uint64_t virtaddr)
+{
+ Elf32_Phdr ph32;
+ Elf64_Phdr ph64;
+
+ /*
+ * Loop through all the program headers and find the header with
+ * virtual address in which the "virtaddr" belongs to.
+ */
+ for ( ; num; num--) {
+ if (pread(fd, xph_addr, xph_sizeof, off) <
+ CAST(ssize_t, xph_sizeof)) {
+ if (file_printf(ms,
+ ", can't read elf program header at %jd",
+ (intmax_t)off) == -1)
+ return -1;
+ return 0;
+
+ }
+ off += xph_sizeof;
+
+ if (fsize != SIZE_UNKNOWN && xph_offset > fsize) {
+ /* Perhaps warn here */
+ continue;
+ }
+
+ if (virtaddr >= xph_vaddr && virtaddr < xph_vaddr + xph_filesz)
+ return xph_offset + (virtaddr - xph_vaddr);
+ }
+ return 0;
+}
+
+file_private size_t
+get_string_on_virtaddr(struct magic_set *ms,
+ int swap, int clazz, int fd, off_t ph_off, int ph_num,
+ off_t fsize, uint64_t virtaddr, char *buf, ssize_t buflen)
+{
+ char *bptr;
+ off_t offset;
+
+ if (buflen == 0)
+ return 0;
+
+ offset = get_offset_from_virtaddr(ms, swap, clazz, fd, ph_off, ph_num,
+ fsize, virtaddr);
+ if (offset < 0 ||
+ (buflen = pread(fd, buf, CAST(size_t, buflen), offset)) <= 0) {
+ (void)file_printf(ms, ", can't read elf string at %jd",
+ (intmax_t)offset);
+ return 0;
+ }
+
+ buf[buflen - 1] = '\0';
+
+ /* We expect only printable characters, so return if buffer contains
+ * non-printable character before the '\0' or just '\0'. */
+ for (bptr = buf; *bptr && isprint(CAST(unsigned char, *bptr)); bptr++)
+ continue;
+ if (*bptr != '\0')
+ return 0;
+
+ return bptr - buf;
+}
+
+
+/*ARGSUSED*/
+file_private int
+do_auxv_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
+ int swap, uint32_t namesz __attribute__((__unused__)),
+ uint32_t descsz __attribute__((__unused__)),
+ size_t noff __attribute__((__unused__)), size_t doff,
+ int *flags, size_t size __attribute__((__unused__)), int clazz,
+ int fd, off_t ph_off, int ph_num, off_t fsize)
+{
+#ifdef ELFCORE
+ Aux32Info auxv32;
+ Aux64Info auxv64;
+ size_t elsize = xauxv_sizeof;
+ const char *tag;
+ int is_string;
+ size_t nval, off;
+
+ if ((*flags & (FLAGS_IS_CORE|FLAGS_DID_CORE_STYLE)) !=
+ (FLAGS_IS_CORE|FLAGS_DID_CORE_STYLE))
+ return 0;
+
+ switch (*flags & FLAGS_CORE_STYLE) {
+ case OS_STYLE_SVR4:
+ if (type != NT_AUXV)
+ return 0;
+ break;
+#ifdef notyet
+ case OS_STYLE_NETBSD:
+ if (type != NT_NETBSD_CORE_AUXV)
+ return 0;
+ break;
+ case OS_STYLE_FREEBSD:
+ if (type != NT_FREEBSD_PROCSTAT_AUXV)
+ return 0;
+ break;
+#endif
+ default:
+ return 0;
+ }
+
+ *flags |= FLAGS_DID_AUXV;
+
+ nval = 0;
+ for (off = 0; off + elsize <= descsz; off += elsize) {
+ memcpy(xauxv_addr, &nbuf[doff + off], xauxv_sizeof);
+ /* Limit processing to 50 vector entries to prevent DoS */
+ if (nval++ >= 50) {
+ file_error(ms, 0, "Too many ELF Auxv elements");
+ return 1;
+ }
+
+ switch(xauxv_type) {
+ case AT_LINUX_EXECFN:
+ is_string = 1;
+ tag = "execfn";
+ break;
+ case AT_LINUX_PLATFORM:
+ is_string = 1;
+ tag = "platform";
+ break;
+ case AT_LINUX_UID:
+ is_string = 0;
+ tag = "real uid";
+ break;
+ case AT_LINUX_GID:
+ is_string = 0;
+ tag = "real gid";
+ break;
+ case AT_LINUX_EUID:
+ is_string = 0;
+ tag = "effective uid";
+ break;
+ case AT_LINUX_EGID:
+ is_string = 0;
+ tag = "effective gid";
+ break;
+ default:
+ is_string = 0;
+ tag = NULL;
+ break;
+ }
+
+ if (tag == NULL)
+ continue;
+
+ if (is_string) {
+ char buf[256];
+ ssize_t buflen;
+ buflen = get_string_on_virtaddr(ms, swap, clazz, fd,
+ ph_off, ph_num, fsize, xauxv_val, buf, sizeof(buf));
+
+ if (buflen == 0)
+ continue;
+
+ if (file_printf(ms, ", %s: '%s'", tag, buf) == -1)
+ return -1;
+ } else {
+ if (file_printf(ms, ", %s: %d", tag,
+ CAST(int, xauxv_val)) == -1)
+ return -1;
+ }
+ }
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+file_private size_t
+dodynamic(struct magic_set *ms, void *vbuf, size_t offset, size_t size,
+ int clazz, int swap, int *pie, size_t *need)
+{
+ Elf32_Dyn dh32;
+ Elf64_Dyn dh64;
+ unsigned char *dbuf = CAST(unsigned char *, vbuf);
+
+ if (xdh_sizeof + offset > size) {
+ /*
+ * We're out of note headers.
+ */
+ return xdh_sizeof + offset;
+ }
+
+ memcpy(xdh_addr, &dbuf[offset], xdh_sizeof);
+ offset += xdh_sizeof;
+
+ switch (xdh_tag) {
+ case DT_FLAGS_1:
+ *pie = 1;
+ if (xdh_val & DF_1_PIE)
+ ms->mode |= 0111;
+ else
+ ms->mode &= ~0111;
+ break;
+ case DT_NEEDED:
+ (*need)++;
+ break;
+ default:
+ break;
+ }
+ return offset;
+}
+
+
+file_private size_t
+donote(struct magic_set *ms, void *vbuf, size_t offset, size_t size,
+ int clazz, int swap, size_t align, int *flags, uint16_t *notecount,
+ int fd, off_t ph_off, int ph_num, off_t fsize)
+{
+ Elf32_Nhdr nh32;
+ Elf64_Nhdr nh64;
+ size_t noff, doff;
+ uint32_t namesz, descsz;
+ char buf[256];
+ unsigned char *nbuf = CAST(unsigned char *, vbuf);
+
+ if (*notecount == 0)
+ return 0;
+ --*notecount;
+
+ if (xnh_sizeof + offset > size) {
+ /*
+ * We're out of note headers.
+ */
+ return xnh_sizeof + offset;
+ }
+ /*XXX: GCC */
+ memset(&nh32, 0, sizeof(nh32));
+ memset(&nh64, 0, sizeof(nh64));
+
+ memcpy(xnh_addr, &nbuf[offset], xnh_sizeof);
+ offset += xnh_sizeof;
+
+ namesz = xnh_namesz;
+ descsz = xnh_descsz;
+
+ if ((namesz == 0) && (descsz == 0)) {
+ /*
+ * We're out of note headers.
+ */
+ return (offset >= size) ? offset : size;
+ }
+
+ if (namesz & 0x80000000) {
+ (void)file_printf(ms, ", bad note name size %#lx",
+ CAST(unsigned long, namesz));
+ return 0;
+ }
+
+ if (descsz & 0x80000000) {
+ (void)file_printf(ms, ", bad note description size %#lx",
+ CAST(unsigned long, descsz));
+ return 0;
+ }
+
+ noff = offset;
+ doff = ELF_ALIGN(offset + namesz);
+
+ if (offset + namesz > size) {
+ /*
+ * We're past the end of the buffer.
+ */
+ return doff;
+ }
+
+ offset = ELF_ALIGN(doff + descsz);
+ if (doff + descsz > size) {
+ /*
+ * We're past the end of the buffer.
+ */
+ return (offset >= size) ? offset : size;
+ }
+
+
+ if ((*flags & FLAGS_DID_OS_NOTE) == 0) {
+ if (do_os_note(ms, nbuf, xnh_type, swap,
+ namesz, descsz, noff, doff, flags))
+ return offset;
+ }
+
+ if ((*flags & FLAGS_DID_BUILD_ID) == 0) {
+ if (do_bid_note(ms, nbuf, xnh_type, swap,
+ namesz, descsz, noff, doff, flags))
+ return offset;
+ }
+
+ if ((*flags & FLAGS_DID_NETBSD_PAX) == 0) {
+ if (do_pax_note(ms, nbuf, xnh_type, swap,
+ namesz, descsz, noff, doff, flags))
+ return offset;
+ }
+
+ if ((*flags & FLAGS_DID_CORE) == 0) {
+ if (do_core_note(ms, nbuf, xnh_type, swap,
+ namesz, descsz, noff, doff, flags, size, clazz))
+ return offset;
+ }
+
+ if ((*flags & FLAGS_DID_AUXV) == 0) {
+ if (do_auxv_note(ms, nbuf, xnh_type, swap,
+ namesz, descsz, noff, doff, flags, size, clazz,
+ fd, ph_off, ph_num, fsize))
+ return offset;
+ }
+
+ if (namesz == 7 && strcmp(RCAST(char *, &nbuf[noff]), "NetBSD") == 0) {
+ int descw, flag;
+ const char *str, *tag;
+ if (descsz > 100)
+ descsz = 100;
+ switch (xnh_type) {
+ case NT_NETBSD_VERSION:
+ return offset;
+ case NT_NETBSD_MARCH:
+ flag = FLAGS_DID_NETBSD_MARCH;
+ tag = "compiled for";
+ break;
+ case NT_NETBSD_CMODEL:
+ flag = FLAGS_DID_NETBSD_CMODEL;
+ tag = "compiler model";
+ break;
+ case NT_NETBSD_EMULATION:
+ flag = FLAGS_DID_NETBSD_EMULATION;
+ tag = "emulation:";
+ break;
+ default:
+ if (*flags & FLAGS_DID_NETBSD_UNKNOWN)
+ return offset;
+ *flags |= FLAGS_DID_NETBSD_UNKNOWN;
+ if (file_printf(ms, ", note=%u", xnh_type) == -1)
+ return offset;
+ return offset;
+ }
+
+ if (*flags & flag)
+ return offset;
+ str = RCAST(const char *, &nbuf[doff]);
+ descw = CAST(int, descsz);
+ *flags |= flag;
+ file_printf(ms, ", %s: %s", tag,
+ file_copystr(buf, sizeof(buf), descw, str));
+ return offset;
+ }
+
+ return offset;
+}
+
+/* SunOS 5.x hardware capability descriptions */
+typedef struct cap_desc {
+ uint64_t cd_mask;
+ const char *cd_name;
+} cap_desc_t;
+
+static const cap_desc_t cap_desc_sparc[] = {
+ { AV_SPARC_MUL32, "MUL32" },
+ { AV_SPARC_DIV32, "DIV32" },
+ { AV_SPARC_FSMULD, "FSMULD" },
+ { AV_SPARC_V8PLUS, "V8PLUS" },
+ { AV_SPARC_POPC, "POPC" },
+ { AV_SPARC_VIS, "VIS" },
+ { AV_SPARC_VIS2, "VIS2" },
+ { AV_SPARC_ASI_BLK_INIT, "ASI_BLK_INIT" },
+ { AV_SPARC_FMAF, "FMAF" },
+ { AV_SPARC_FJFMAU, "FJFMAU" },
+ { AV_SPARC_IMA, "IMA" },
+ { 0, NULL }
+};
+
+static const cap_desc_t cap_desc_386[] = {
+ { AV_386_FPU, "FPU" },
+ { AV_386_TSC, "TSC" },
+ { AV_386_CX8, "CX8" },
+ { AV_386_SEP, "SEP" },
+ { AV_386_AMD_SYSC, "AMD_SYSC" },
+ { AV_386_CMOV, "CMOV" },
+ { AV_386_MMX, "MMX" },
+ { AV_386_AMD_MMX, "AMD_MMX" },
+ { AV_386_AMD_3DNow, "AMD_3DNow" },
+ { AV_386_AMD_3DNowx, "AMD_3DNowx" },
+ { AV_386_FXSR, "FXSR" },
+ { AV_386_SSE, "SSE" },
+ { AV_386_SSE2, "SSE2" },
+ { AV_386_PAUSE, "PAUSE" },
+ { AV_386_SSE3, "SSE3" },
+ { AV_386_MON, "MON" },
+ { AV_386_CX16, "CX16" },
+ { AV_386_AHF, "AHF" },
+ { AV_386_TSCP, "TSCP" },
+ { AV_386_AMD_SSE4A, "AMD_SSE4A" },
+ { AV_386_POPCNT, "POPCNT" },
+ { AV_386_AMD_LZCNT, "AMD_LZCNT" },
+ { AV_386_SSSE3, "SSSE3" },
+ { AV_386_SSE4_1, "SSE4.1" },
+ { AV_386_SSE4_2, "SSE4.2" },
+ { 0, NULL }
+};
+
+file_private int
+doshn(struct magic_set *ms, int clazz, int swap, int fd, off_t off, int num,
+ size_t size, off_t fsize, int mach, int strtab, int *flags,
+ uint16_t *notecount)
+{
+ Elf32_Shdr sh32;
+ Elf64_Shdr sh64;
+ int stripped = 1, has_debug_info = 0;
+ size_t nbadcap = 0;
+ void *nbuf;
+ off_t noff, coff, name_off, offs;
+ uint64_t cap_hw1 = 0; /* SunOS 5.x hardware capabilities */
+ uint64_t cap_sf1 = 0; /* SunOS 5.x software capabilities */
+ char name[50];
+ ssize_t namesize;
+
+ if (ms->flags & MAGIC_MIME)
+ return 0;
+
+ if (num == 0) {
+ if (file_printf(ms, ", no section header") == -1)
+ return -1;
+ return 0;
+ }
+ if (size != xsh_sizeof) {
+ if (file_printf(ms, ", corrupted section header size") == -1)
+ return -1;
+ return 0;
+ }
+
+ /* Read offset of name section to be able to read section names later */
+ offs = CAST(off_t, (off + size * strtab));
+ if (pread(fd, xsh_addr, xsh_sizeof, offs) < CAST(ssize_t, xsh_sizeof)) {
+ if (file_printf(ms, ", missing section headers at %jd",
+ (intmax_t)offs) == -1)
+ return -1;
+ return 0;
+ }
+ name_off = xsh_offset;
+
+ if (fsize != SIZE_UNKNOWN && fsize < name_off) {
+ if (file_printf(ms, ", too large section header offset %jd",
+ (intmax_t)name_off) == -1)
+ return -1;
+ return 0;
+ }
+
+ for ( ; num; num--) {
+ /* Read the name of this section. */
+ offs = name_off + xsh_name;
+ if ((namesize = pread(fd, name, sizeof(name) - 1, offs))
+ == -1) {
+ if (file_printf(ms,
+ ", can't read name of elf section at %jd",
+ (intmax_t)offs) == -1)
+ return -1;
+ return 0;
+ }
+ name[namesize] = '\0';
+ if (strcmp(name, ".debug_info") == 0) {
+ has_debug_info = 1;
+ stripped = 0;
+ }
+
+ if (pread(fd, xsh_addr, xsh_sizeof, off) <
+ CAST(ssize_t, xsh_sizeof)) {
+ if (file_printf(ms, ", can't read elf section at %jd",
+ (intmax_t)off) == -1)
+ return -1;
+ return 0;
+ }
+ off += size;
+
+ /* Things we can determine before we seek */
+ switch (xsh_type) {
+ case SHT_SYMTAB:
+#if 0
+ case SHT_DYNSYM:
+#endif
+ stripped = 0;
+ break;
+ default:
+ if (fsize != SIZE_UNKNOWN && xsh_offset > fsize) {
+ /* Perhaps warn here */
+ continue;
+ }
+ break;
+ }
+
+
+ /* Things we can determine when we seek */
+ switch (xsh_type) {
+ case SHT_NOTE:
+ if (CAST(uintmax_t, (xsh_size + xsh_offset)) >
+ CAST(uintmax_t, fsize)) {
+ if (file_printf(ms,
+ ", note offset/size %#" INTMAX_T_FORMAT
+ "x+%#" INTMAX_T_FORMAT "x exceeds"
+ " file size %#" INTMAX_T_FORMAT "x",
+ CAST(uintmax_t, xsh_offset),
+ CAST(uintmax_t, xsh_size),
+ CAST(uintmax_t, fsize)) == -1)
+ return -1;
+ return 0;
+ }
+ if (xsh_size > MAX_SHSIZE) {
+ file_error(ms, errno, "Note section size too "
+ "big (%ju > %u)", (uintmax_t)xsh_size,
+ MAX_SHSIZE);
+ return -1;
+ }
+ if ((nbuf = malloc(xsh_size)) == NULL) {
+ file_error(ms, errno, "Cannot allocate memory"
+ " for note");
+ return -1;
+ }
+ offs = xsh_offset;
+ if (pread(fd, nbuf, xsh_size, offs) <
+ CAST(ssize_t, xsh_size)) {
+ free(nbuf);
+ if (file_printf(ms,
+ ", can't read elf note at %jd",
+ (intmax_t)offs) == -1)
+ return -1;
+ return 0;
+ }
+
+ noff = 0;
+ for (;;) {
+ if (noff >= CAST(off_t, xsh_size))
+ break;
+ noff = donote(ms, nbuf, CAST(size_t, noff),
+ xsh_size, clazz, swap, 4, flags, notecount,
+ fd, 0, 0, 0);
+ if (noff == 0)
+ break;
+ }
+ free(nbuf);
+ break;
+ case SHT_SUNW_cap:
+ switch (mach) {
+ case EM_SPARC:
+ case EM_SPARCV9:
+ case EM_IA_64:
+ case EM_386:
+ case EM_AMD64:
+ break;
+ default:
+ goto skip;
+ }
+
+ if (nbadcap > 5)
+ break;
+ if (lseek(fd, xsh_offset, SEEK_SET)
+ == CAST(off_t, -1)) {
+ file_badseek(ms);
+ return -1;
+ }
+ coff = 0;
+ for (;;) {
+ Elf32_Cap cap32;
+ Elf64_Cap cap64;
+ char cbuf[/*CONSTCOND*/
+ MAX(sizeof(cap32), sizeof(cap64))];
+ if ((coff += xcap_sizeof) >
+ CAST(off_t, xsh_size))
+ break;
+ if (read(fd, cbuf, CAST(size_t, xcap_sizeof)) !=
+ CAST(ssize_t, xcap_sizeof)) {
+ file_badread(ms);
+ return -1;
+ }
+ if (cbuf[0] == 'A') {
+#ifdef notyet
+ char *p = cbuf + 1;
+ uint32_t len, tag;
+ memcpy(&len, p, sizeof(len));
+ p += 4;
+ len = getu32(swap, len);
+ if (memcmp("gnu", p, 3) != 0) {
+ if (file_printf(ms,
+ ", unknown capability %.3s", p)
+ == -1)
+ return -1;
+ break;
+ }
+ p += strlen(p) + 1;
+ tag = *p++;
+ memcpy(&len, p, sizeof(len));
+ p += 4;
+ len = getu32(swap, len);
+ if (tag != 1) {
+ if (file_printf(ms, ", unknown gnu"
+ " capability tag %d", tag)
+ == -1)
+ return -1;
+ break;
+ }
+ // gnu attributes
+#endif
+ break;
+ }
+ memcpy(xcap_addr, cbuf, xcap_sizeof);
+ switch (xcap_tag) {
+ case CA_SUNW_NULL:
+ break;
+ case CA_SUNW_HW_1:
+ cap_hw1 |= xcap_val;
+ break;
+ case CA_SUNW_SF_1:
+ cap_sf1 |= xcap_val;
+ break;
+ default:
+ if (file_printf(ms,
+ ", with unknown capability "
+ "%#" INT64_T_FORMAT "x = %#"
+ INT64_T_FORMAT "x",
+ CAST(unsigned long long, xcap_tag),
+ CAST(unsigned long long, xcap_val))
+ == -1)
+ return -1;
+ if (nbadcap++ > 2)
+ coff = xsh_size;
+ break;
+ }
+ }
+ /*FALLTHROUGH*/
+ skip:
+ default:
+ break;
+ }
+ }
+
+ if (has_debug_info) {
+ if (file_printf(ms, ", with debug_info") == -1)
+ return -1;
+ }
+ if (file_printf(ms, ", %sstripped", stripped ? "" : "not ") == -1)
+ return -1;
+ if (cap_hw1) {
+ const cap_desc_t *cdp;
+ switch (mach) {
+ case EM_SPARC:
+ case EM_SPARC32PLUS:
+ case EM_SPARCV9:
+ cdp = cap_desc_sparc;
+ break;
+ case EM_386:
+ case EM_IA_64:
+ case EM_AMD64:
+ cdp = cap_desc_386;
+ break;
+ default:
+ cdp = NULL;
+ break;
+ }
+ if (file_printf(ms, ", uses") == -1)
+ return -1;
+ if (cdp) {
+ while (cdp->cd_name) {
+ if (cap_hw1 & cdp->cd_mask) {
+ if (file_printf(ms,
+ " %s", cdp->cd_name) == -1)
+ return -1;
+ cap_hw1 &= ~cdp->cd_mask;
+ }
+ ++cdp;
+ }
+ if (cap_hw1)
+ if (file_printf(ms,
+ " unknown hardware capability %#"
+ INT64_T_FORMAT "x",
+ CAST(unsigned long long, cap_hw1)) == -1)
+ return -1;
+ } else {
+ if (file_printf(ms,
+ " hardware capability %#" INT64_T_FORMAT "x",
+ CAST(unsigned long long, cap_hw1)) == -1)
+ return -1;
+ }
+ }
+ if (cap_sf1) {
+ if (cap_sf1 & SF1_SUNW_FPUSED) {
+ if (file_printf(ms,
+ (cap_sf1 & SF1_SUNW_FPKNWN)
+ ? ", uses frame pointer"
+ : ", not known to use frame pointer") == -1)
+ return -1;
+ }
+ cap_sf1 &= ~SF1_SUNW_MASK;
+ if (cap_sf1)
+ if (file_printf(ms,
+ ", with unknown software capability %#"
+ INT64_T_FORMAT "x",
+ CAST(unsigned long long, cap_sf1)) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Look through the program headers of an executable image, to determine
+ * if it is statically or dynamically linked. If it has a dynamic section,
+ * it is pie, and does not have an interpreter or needed libraries, we
+ * call it static pie.
+ */
+file_private int
+dophn_exec(struct magic_set *ms, int clazz, int swap, int fd, off_t off,
+ int num, size_t size, off_t fsize, int sh_num, int *flags,
+ uint16_t *notecount)
+{
+ Elf32_Phdr ph32;
+ Elf64_Phdr ph64;
+ const char *linking_style;
+ unsigned char nbuf[BUFSIZ];
+ char ibuf[BUFSIZ];
+ char interp[BUFSIZ];
+ ssize_t bufsize;
+ size_t offset, align, need = 0;
+ int pie = 0, dynamic = 0;
+
+ if (num == 0) {
+ if (file_printf(ms, ", no program header") == -1)
+ return -1;
+ return 0;
+ }
+ if (size != xph_sizeof) {
+ if (file_printf(ms, ", corrupted program header size") == -1)
+ return -1;
+ return 0;
+ }
+
+ interp[0] = '\0';
+ for ( ; num; num--) {
+ int doread;
+ if (pread(fd, xph_addr, xph_sizeof, off) <
+ CAST(ssize_t, xph_sizeof)) {
+ if (file_printf(ms,
+ ", can't read elf program headers at %jd",
+ (intmax_t)off) == -1)
+ return -1;
+ return 0;
+ }
+
+ off += size;
+ bufsize = 0;
+ align = 4;
+
+ /* Things we can determine before we seek */
+ switch (xph_type) {
+ case PT_DYNAMIC:
+ doread = 1;
+ break;
+ case PT_NOTE:
+ if (sh_num) /* Did this through section headers */
+ continue;
+ if (((align = xph_align) & 0x80000000UL) != 0 ||
+ align < 4) {
+ if (file_printf(ms,
+ ", invalid note alignment %#lx",
+ CAST(unsigned long, align)) == -1)
+ return -1;
+ align = 4;
+ }
+ /*FALLTHROUGH*/
+ case PT_INTERP:
+ doread = 1;
+ break;
+ default:
+ doread = 0;
+ if (fsize != SIZE_UNKNOWN && xph_offset > fsize) {
+ /* Maybe warn here? */
+ continue;
+ }
+ break;
+ }
+
+ if (doread) {
+ size_t len = xph_filesz < sizeof(nbuf) ? xph_filesz
+ : sizeof(nbuf);
+ off_t offs = xph_offset;
+ bufsize = pread(fd, nbuf, len, offs);
+ if (bufsize == -1) {
+ if (file_printf(ms,
+ ", can't read section at %jd",
+ (intmax_t)offs) == -1)
+ return -1;
+ return 0;
+ }
+ }
+
+ /* Things we can determine when we seek */
+ switch (xph_type) {
+ case PT_DYNAMIC:
+ dynamic = 1;
+ offset = 0;
+ // Let DF_1 determine if we are PIE or not.
+ ms->mode &= ~0111;
+ for (;;) {
+ if (offset >= CAST(size_t, bufsize))
+ break;
+ offset = dodynamic(ms, nbuf, offset,
+ CAST(size_t, bufsize), clazz, swap,
+ &pie, &need);
+ if (offset == 0)
+ break;
+ }
+ if (ms->flags & MAGIC_MIME)
+ continue;
+ break;
+
+ case PT_INTERP:
+ need++;
+ if (ms->flags & MAGIC_MIME)
+ continue;
+ if (bufsize && nbuf[0]) {
+ nbuf[bufsize - 1] = '\0';
+ memcpy(interp, nbuf, CAST(size_t, bufsize));
+ } else
+ strlcpy(interp, "*empty*", sizeof(interp));
+ break;
+ case PT_NOTE:
+ if (ms->flags & MAGIC_MIME)
+ return 0;
+ /*
+ * This is a PT_NOTE section; loop through all the notes
+ * in the section.
+ */
+ offset = 0;
+ for (;;) {
+ if (offset >= CAST(size_t, bufsize))
+ break;
+ offset = donote(ms, nbuf, offset,
+ CAST(size_t, bufsize), clazz, swap, align,
+ flags, notecount, fd, 0, 0, 0);
+ if (offset == 0)
+ break;
+ }
+ break;
+ default:
+ if (ms->flags & MAGIC_MIME)
+ continue;
+ break;
+ }
+ }
+ if (ms->flags & MAGIC_MIME)
+ return 0;
+ if (dynamic) {
+ if (pie && need == 0)
+ linking_style = "static-pie";
+ else
+ linking_style = "dynamically";
+ } else {
+ linking_style = "statically";
+ }
+ if (file_printf(ms, ", %s linked", linking_style) == -1)
+ return -1;
+ if (interp[0])
+ if (file_printf(ms, ", interpreter %s", file_printable(ms,
+ ibuf, sizeof(ibuf), interp, sizeof(interp))) == -1)
+ return -1;
+ return 0;
+}
+
+
+file_protected int
+file_tryelf(struct magic_set *ms, const struct buffer *b)
+{
+ int fd = b->fd;
+ const unsigned char *buf = CAST(const unsigned char *, b->fbuf);
+ size_t nbytes = b->flen;
+ union {
+ int32_t l;
+ char c[sizeof(int32_t)];
+ } u;
+ int clazz;
+ int swap;
+ struct stat st;
+ const struct stat *stp;
+ off_t fsize;
+ int flags = 0;
+ Elf32_Ehdr elf32hdr;
+ Elf64_Ehdr elf64hdr;
+ uint16_t type, phnum, shnum, notecount;
+
+ if (ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION))
+ return 0;
+ /*
+ * ELF executables have multiple section headers in arbitrary
+ * file locations and thus file(1) cannot determine it from easily.
+ * Instead we traverse thru all section headers until a symbol table
+ * one is found or else the binary is stripped.
+ * Return immediately if it's not ELF (so we avoid pipe2file unless
+ * needed).
+ */
+ if (buf[EI_MAG0] != ELFMAG0
+ || (buf[EI_MAG1] != ELFMAG1 && buf[EI_MAG1] != OLFMAG1)
+ || buf[EI_MAG2] != ELFMAG2 || buf[EI_MAG3] != ELFMAG3)
+ return 0;
+
+ /*
+ * If we cannot seek, it must be a pipe, socket or fifo.
+ */
+ if((lseek(fd, CAST(off_t, 0), SEEK_SET) == CAST(off_t, -1))
+ && (errno == ESPIPE))
+ fd = file_pipe2file(ms, fd, buf, nbytes);
+
+ if (fd == -1) {
+ file_badread(ms);
+ return -1;
+ }
+
+ stp = &b->st;
+ /*
+ * b->st.st_size != 0 if previous fstat() succeeded,
+ * which is likely, we can avoid extra stat() call.
+ */
+ if (b->st.st_size == 0) {
+ stp = &st;
+ if (fstat(fd, &st) == -1) {
+ file_badread(ms);
+ return -1;
+ }
+ }
+ if (S_ISREG(stp->st_mode) || stp->st_size != 0)
+ fsize = stp->st_size;
+ else
+ fsize = SIZE_UNKNOWN;
+
+ clazz = buf[EI_CLASS];
+
+ switch (clazz) {
+ case ELFCLASS32:
+#undef elf_getu
+#define elf_getu(a, b) elf_getu32(a, b)
+#undef elfhdr
+#define elfhdr elf32hdr
+#include "elfclass.h"
+ case ELFCLASS64:
+#undef elf_getu
+#define elf_getu(a, b) elf_getu64(a, b)
+#undef elfhdr
+#define elfhdr elf64hdr
+#include "elfclass.h"
+ default:
+ if (file_printf(ms, ", unknown class %d", clazz) == -1)
+ return -1;
+ break;
+ }
+ return 0;
+}
+#endif
diff --git a/src/readelf.h b/src/readelf.h
new file mode 100644
index 0000000..809d3f7
--- /dev/null
+++ b/src/readelf.h
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) Christos Zoulas 2003.
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * @(#)Id: readelf.h,v 1.9 2002/05/16 18:45:56 christos Exp
+ *
+ * Provide elf data structures for non-elf machines, allowing file
+ * non-elf hosts to determine if an elf binary is stripped.
+ * Note: cobbled from the linux header file, with modifications
+ */
+#ifndef __fake_elf_h__
+#define __fake_elf_h__
+
+#if HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+typedef uint32_t Elf32_Addr;
+typedef uint32_t Elf32_Off;
+typedef uint16_t Elf32_Half;
+typedef uint32_t Elf32_Word;
+typedef uint8_t Elf32_Char;
+
+typedef uint64_t Elf64_Addr;
+typedef uint64_t Elf64_Off;
+typedef uint64_t Elf64_Xword;
+typedef uint16_t Elf64_Half;
+typedef uint32_t Elf64_Word;
+typedef uint8_t Elf64_Char;
+
+#define EI_NIDENT 16
+
+typedef struct {
+ Elf32_Word a_type; /* 32-bit id */
+ Elf32_Word a_v; /* 32-bit id */
+} Aux32Info;
+
+typedef struct {
+ Elf64_Xword a_type; /* 64-bit id */
+ Elf64_Xword a_v; /* 64-bit id */
+} Aux64Info;
+
+#define AT_NULL 0 /* end of vector */
+#define AT_IGNORE 1 /* entry should be ignored */
+#define AT_EXECFD 2 /* file descriptor of program */
+#define AT_PHDR 3 /* program headers for program */
+#define AT_PHENT 4 /* size of program header entry */
+#define AT_PHNUM 5 /* number of program headers */
+#define AT_PAGESZ 6 /* system page size */
+#define AT_BASE 7 /* base address of interpreter */
+#define AT_FLAGS 8 /* flags */
+#define AT_ENTRY 9 /* entry point of program */
+#define AT_LINUX_NOTELF 10 /* program is not ELF */
+#define AT_LINUX_UID 11 /* real uid */
+#define AT_LINUX_EUID 12 /* effective uid */
+#define AT_LINUX_GID 13 /* real gid */
+#define AT_LINUX_EGID 14 /* effective gid */
+#define AT_LINUX_PLATFORM 15 /* string identifying CPU for optimizations */
+#define AT_LINUX_HWCAP 16 /* arch dependent hints at CPU capabilities */
+#define AT_LINUX_CLKTCK 17 /* frequency at which times() increments */
+/* AT_* values 18 through 22 are reserved */
+#define AT_LINUX_SECURE 23 /* secure mode boolean */
+#define AT_LINUX_BASE_PLATFORM 24 /* string identifying real platform, may
+ * differ from AT_PLATFORM. */
+#define AT_LINUX_RANDOM 25 /* address of 16 random bytes */
+#define AT_LINUX_HWCAP2 26 /* extension of AT_HWCAP */
+#define AT_LINUX_EXECFN 31 /* filename of program */
+
+typedef struct {
+ Elf32_Char e_ident[EI_NIDENT];
+ Elf32_Half e_type;
+ Elf32_Half e_machine;
+ Elf32_Word e_version;
+ Elf32_Addr e_entry; /* Entry point */
+ Elf32_Off e_phoff;
+ Elf32_Off e_shoff;
+ Elf32_Word e_flags;
+ Elf32_Half e_ehsize;
+ Elf32_Half e_phentsize;
+ Elf32_Half e_phnum;
+ Elf32_Half e_shentsize;
+ Elf32_Half e_shnum;
+ Elf32_Half e_shstrndx;
+} Elf32_Ehdr;
+
+typedef struct {
+ Elf64_Char e_ident[EI_NIDENT];
+ Elf64_Half e_type;
+ Elf64_Half e_machine;
+ Elf64_Word e_version;
+ Elf64_Addr e_entry; /* Entry point */
+ Elf64_Off e_phoff;
+ Elf64_Off e_shoff;
+ Elf64_Word e_flags;
+ Elf64_Half e_ehsize;
+ Elf64_Half e_phentsize;
+ Elf64_Half e_phnum;
+ Elf64_Half e_shentsize;
+ Elf64_Half e_shnum;
+ Elf64_Half e_shstrndx;
+} Elf64_Ehdr;
+
+/* e_type */
+#define ET_REL 1
+#define ET_EXEC 2
+#define ET_DYN 3
+#define ET_CORE 4
+
+/* e_machine (used only for SunOS 5.x hardware capabilities) */
+#define EM_SPARC 2
+#define EM_386 3
+#define EM_SPARC32PLUS 18
+#define EM_SPARCV9 43
+#define EM_IA_64 50
+#define EM_AMD64 62
+
+/* sh_type */
+#define SHT_SYMTAB 2
+#define SHT_NOTE 7
+#define SHT_DYNSYM 11
+#define SHT_SUNW_cap 0x6ffffff5 /* SunOS 5.x hw/sw capabilities */
+
+/* elf type */
+#define ELFDATANONE 0 /* e_ident[EI_DATA] */
+#define ELFDATA2LSB 1
+#define ELFDATA2MSB 2
+
+/* elf class */
+#define ELFCLASSNONE 0
+#define ELFCLASS32 1
+#define ELFCLASS64 2
+
+/* magic number */
+#define EI_MAG0 0 /* e_ident[] indexes */
+#define EI_MAG1 1
+#define EI_MAG2 2
+#define EI_MAG3 3
+#define EI_CLASS 4
+#define EI_DATA 5
+#define EI_VERSION 6
+#define EI_PAD 7
+
+#define ELFMAG0 0x7f /* EI_MAG */
+#define ELFMAG1 'E'
+#define ELFMAG2 'L'
+#define ELFMAG3 'F'
+#define ELFMAG "\177ELF"
+
+#define OLFMAG1 'O'
+#define OLFMAG "\177OLF"
+
+typedef struct {
+ Elf32_Word p_type;
+ Elf32_Off p_offset;
+ Elf32_Addr p_vaddr;
+ Elf32_Addr p_paddr;
+ Elf32_Word p_filesz;
+ Elf32_Word p_memsz;
+ Elf32_Word p_flags;
+ Elf32_Word p_align;
+} Elf32_Phdr;
+
+typedef struct {
+ Elf64_Word p_type;
+ Elf64_Word p_flags;
+ Elf64_Off p_offset;
+ Elf64_Addr p_vaddr;
+ Elf64_Addr p_paddr;
+ Elf64_Xword p_filesz;
+ Elf64_Xword p_memsz;
+ Elf64_Xword p_align;
+} Elf64_Phdr;
+
+#define PT_NULL 0 /* p_type */
+#define PT_LOAD 1
+#define PT_DYNAMIC 2
+#define PT_INTERP 3
+#define PT_NOTE 4
+#define PT_SHLIB 5
+#define PT_PHDR 6
+#define PT_NUM 7
+
+typedef struct {
+ Elf32_Word sh_name;
+ Elf32_Word sh_type;
+ Elf32_Word sh_flags;
+ Elf32_Addr sh_addr;
+ Elf32_Off sh_offset;
+ Elf32_Word sh_size;
+ Elf32_Word sh_link;
+ Elf32_Word sh_info;
+ Elf32_Word sh_addralign;
+ Elf32_Word sh_entsize;
+} Elf32_Shdr;
+
+typedef struct {
+ Elf64_Word sh_name;
+ Elf64_Word sh_type;
+ Elf64_Off sh_flags;
+ Elf64_Addr sh_addr;
+ Elf64_Off sh_offset;
+ Elf64_Off sh_size;
+ Elf64_Word sh_link;
+ Elf64_Word sh_info;
+ Elf64_Off sh_addralign;
+ Elf64_Off sh_entsize;
+} Elf64_Shdr;
+
+#define NT_NETBSD_CORE_PROCINFO 1
+#define NT_NETBSD_CORE_AUXV 2
+
+struct NetBSD_elfcore_procinfo {
+ /* Version 1 fields start here. */
+ uint32_t cpi_version; /* our version */
+ uint32_t cpi_cpisize; /* sizeof(this struct) */
+ uint32_t cpi_signo; /* killing signal */
+ uint32_t cpi_sigcode; /* signal code */
+ uint32_t cpi_sigpend[4]; /* pending signals */
+ uint32_t cpi_sigmask[4]; /* blocked signals */
+ uint32_t cpi_sigignore[4]; /* ignored signals */
+ uint32_t cpi_sigcatch[4]; /* caught signals */
+ int32_t cpi_pid; /* process ID */
+ int32_t cpi_ppid; /* parent process ID */
+ int32_t cpi_pgrp; /* process group ID */
+ int32_t cpi_sid; /* session ID */
+ uint32_t cpi_ruid; /* real user ID */
+ uint32_t cpi_euid; /* effective user ID */
+ uint32_t cpi_svuid; /* saved user ID */
+ uint32_t cpi_rgid; /* real group ID */
+ uint32_t cpi_egid; /* effective group ID */
+ uint32_t cpi_svgid; /* saved group ID */
+ uint32_t cpi_nlwps; /* number of LWPs */
+ int8_t cpi_name[32]; /* copy of p->p_comm */
+ /* Add version 2 fields below here. */
+ int32_t cpi_siglwp; /* LWP target of killing signal */
+};
+
+/* Note header in a PT_NOTE section */
+typedef struct elf_note {
+ Elf32_Word n_namesz; /* Name size */
+ Elf32_Word n_descsz; /* Content size */
+ Elf32_Word n_type; /* Content type */
+} Elf32_Nhdr;
+
+typedef struct {
+ Elf64_Word n_namesz;
+ Elf64_Word n_descsz;
+ Elf64_Word n_type;
+} Elf64_Nhdr;
+
+/* Notes used in ET_CORE */
+#define NT_PRSTATUS 1
+#define NT_PRFPREG 2
+#define NT_PRPSINFO 3
+#define NT_PRXREG 4
+#define NT_TASKSTRUCT 4
+#define NT_PLATFORM 5
+#define NT_AUXV 6
+
+/* Note types used in executables */
+/* NetBSD executables (name = "NetBSD") */
+#define NT_NETBSD_VERSION 1
+#define NT_NETBSD_EMULATION 2
+#define NT_FREEBSD_VERSION 1
+#define NT_OPENBSD_VERSION 1
+#define NT_DRAGONFLY_VERSION 1
+/*
+ * GNU executables (name = "GNU")
+ * word[0]: GNU OS tags
+ * word[1]: major version
+ * word[2]: minor version
+ * word[3]: tiny version
+ */
+#define NT_GNU_VERSION 1
+
+/* GNU OS tags */
+#define GNU_OS_LINUX 0
+#define GNU_OS_HURD 1
+#define GNU_OS_SOLARIS 2
+#define GNU_OS_KFREEBSD 3
+#define GNU_OS_KNETBSD 4
+
+/*
+ * GNU Hardware capability information
+ * word[0]: Number of entries
+ * word[1]: Bitmask of enabled entries
+ * Followed by a byte id, and a NUL terminated string per entry
+ */
+#define NT_GNU_HWCAP 2
+
+/*
+ * GNU Build ID generated by ld
+ * 160 bit SHA1 [default]
+ * 128 bit md5 or uuid
+ */
+#define NT_GNU_BUILD_ID 3
+
+/*
+ * NetBSD-specific note type: PaX.
+ * There should be 1 NOTE per executable.
+ * name: PaX\0
+ * namesz: 4
+ * desc:
+ * word[0]: capability bitmask
+ * descsz: 4
+ */
+#define NT_NETBSD_PAX 3
+#define NT_NETBSD_PAX_MPROTECT 0x01 /* Force enable Mprotect */
+#define NT_NETBSD_PAX_NOMPROTECT 0x02 /* Force disable Mprotect */
+#define NT_NETBSD_PAX_GUARD 0x04 /* Force enable Segvguard */
+#define NT_NETBSD_PAX_NOGUARD 0x08 /* Force disable Servguard */
+#define NT_NETBSD_PAX_ASLR 0x10 /* Force enable ASLR */
+#define NT_NETBSD_PAX_NOASLR 0x20 /* Force disable ASLR */
+
+/*
+ * NetBSD-specific note type: MACHINE_ARCH.
+ * There should be 1 NOTE per executable.
+ * name: NetBSD\0
+ * namesz: 7
+ * desc: string
+ * descsz: variable
+ */
+#define NT_NETBSD_MARCH 5
+
+/*
+ * NetBSD-specific note type: COMPILER MODEL.
+ * There should be 1 NOTE per executable.
+ * name: NetBSD\0
+ * namesz: 7
+ * desc: string
+ * descsz: variable
+ */
+#define NT_NETBSD_CMODEL 6
+
+/*
+ * Golang-specific note type
+ * name: Go\0\0
+ * namesz: 4
+ * desc: base-64 build id.
+ * descsz: < 128
+ */
+#define NT_GO_BUILD_ID 4
+
+/*
+ * FreeBSD specific notes
+ */
+#define NT_FREEBSD_PROCSTAT_AUXV 16
+
+#if !defined(ELFSIZE) && defined(ARCH_ELFSIZE)
+#define ELFSIZE ARCH_ELFSIZE
+#endif
+/* SunOS 5.x hardware/software capabilities */
+typedef struct {
+ Elf32_Word c_tag;
+ union {
+ Elf32_Word c_val;
+ Elf32_Addr c_ptr;
+ } c_un;
+} Elf32_Cap;
+
+typedef struct {
+ Elf64_Xword c_tag;
+ union {
+ Elf64_Xword c_val;
+ Elf64_Addr c_ptr;
+ } c_un;
+} Elf64_Cap;
+
+/* SunOS 5.x hardware/software capability tags */
+#define CA_SUNW_NULL 0
+#define CA_SUNW_HW_1 1
+#define CA_SUNW_SF_1 2
+
+/* SunOS 5.x software capabilities */
+#define SF1_SUNW_FPKNWN 0x01
+#define SF1_SUNW_FPUSED 0x02
+#define SF1_SUNW_MASK 0x03
+
+/* SunOS 5.x hardware capabilities: sparc */
+#define AV_SPARC_MUL32 0x0001
+#define AV_SPARC_DIV32 0x0002
+#define AV_SPARC_FSMULD 0x0004
+#define AV_SPARC_V8PLUS 0x0008
+#define AV_SPARC_POPC 0x0010
+#define AV_SPARC_VIS 0x0020
+#define AV_SPARC_VIS2 0x0040
+#define AV_SPARC_ASI_BLK_INIT 0x0080
+#define AV_SPARC_FMAF 0x0100
+#define AV_SPARC_FJFMAU 0x4000
+#define AV_SPARC_IMA 0x8000
+
+/* SunOS 5.x hardware capabilities: 386 */
+#define AV_386_FPU 0x00000001
+#define AV_386_TSC 0x00000002
+#define AV_386_CX8 0x00000004
+#define AV_386_SEP 0x00000008
+#define AV_386_AMD_SYSC 0x00000010
+#define AV_386_CMOV 0x00000020
+#define AV_386_MMX 0x00000040
+#define AV_386_AMD_MMX 0x00000080
+#define AV_386_AMD_3DNow 0x00000100
+#define AV_386_AMD_3DNowx 0x00000200
+#define AV_386_FXSR 0x00000400
+#define AV_386_SSE 0x00000800
+#define AV_386_SSE2 0x00001000
+#define AV_386_PAUSE 0x00002000
+#define AV_386_SSE3 0x00004000
+#define AV_386_MON 0x00008000
+#define AV_386_CX16 0x00010000
+#define AV_386_AHF 0x00020000
+#define AV_386_TSCP 0x00040000
+#define AV_386_AMD_SSE4A 0x00080000
+#define AV_386_POPCNT 0x00100000
+#define AV_386_AMD_LZCNT 0x00200000
+#define AV_386_SSSE3 0x00400000
+#define AV_386_SSE4_1 0x00800000
+#define AV_386_SSE4_2 0x01000000
+
+/*
+ * Dynamic Section structure array
+ */
+typedef struct {
+ Elf32_Word d_tag; /* entry tag value */
+ union {
+ Elf32_Addr d_ptr;
+ Elf32_Word d_val;
+ } d_un;
+} Elf32_Dyn;
+
+typedef struct {
+ Elf64_Xword d_tag; /* entry tag value */
+ union {
+ Elf64_Addr d_ptr;
+ Elf64_Xword d_val;
+ } d_un;
+} Elf64_Dyn;
+
+/* d_tag */
+#define DT_NULL 0 /* Marks end of dynamic array */
+#define DT_NEEDED 1 /* Name of needed library (DT_STRTAB offset) */
+#define DT_PLTRELSZ 2 /* Size, in bytes, of relocations in PLT */
+#define DT_PLTGOT 3 /* Address of PLT and/or GOT */
+#define DT_HASH 4 /* Address of symbol hash table */
+#define DT_STRTAB 5 /* Address of string table */
+#define DT_SYMTAB 6 /* Address of symbol table */
+#define DT_RELA 7 /* Address of Rela relocation table */
+#define DT_RELASZ 8 /* Size, in bytes, of DT_RELA table */
+#define DT_RELAENT 9 /* Size, in bytes, of one DT_RELA entry */
+#define DT_STRSZ 10 /* Size, in bytes, of DT_STRTAB table */
+#define DT_SYMENT 11 /* Size, in bytes, of one DT_SYMTAB entry */
+#define DT_INIT 12 /* Address of initialization function */
+#define DT_FINI 13 /* Address of termination function */
+#define DT_SONAME 14 /* Shared object name (DT_STRTAB offset) */
+#define DT_RPATH 15 /* Library search path (DT_STRTAB offset) */
+#define DT_SYMBOLIC 16 /* Start symbol search within local object */
+#define DT_REL 17 /* Address of Rel relocation table */
+#define DT_RELSZ 18 /* Size, in bytes, of DT_REL table */
+#define DT_RELENT 19 /* Size, in bytes, of one DT_REL entry */
+#define DT_PLTREL 20 /* Type of PLT relocation entries */
+#define DT_DEBUG 21 /* Used for debugging; unspecified */
+#define DT_TEXTREL 22 /* Relocations might modify non-writable seg */
+#define DT_JMPREL 23 /* Address of relocations associated with PLT */
+#define DT_BIND_NOW 24 /* Process all relocations at load-time */
+#define DT_INIT_ARRAY 25 /* Address of initialization function array */
+#define DT_FINI_ARRAY 26 /* Size, in bytes, of DT_INIT_ARRAY array */
+#define DT_INIT_ARRAYSZ 27 /* Address of termination function array */
+#define DT_FINI_ARRAYSZ 28 /* Size, in bytes, of DT_FINI_ARRAY array*/
+#define DT_RUNPATH 29 /* overrides DT_RPATH */
+#define DT_FLAGS 30 /* Encodes ORIGIN, SYMBOLIC, TEXTREL, BIND_NOW, STATIC_TLS */
+#define DT_ENCODING 31 /* ??? */
+#define DT_PREINIT_ARRAY 32 /* Address of pre-init function array */
+#define DT_PREINIT_ARRAYSZ 33 /* Size, in bytes, of DT_PREINIT_ARRAY array */
+#define DT_NUM 34
+
+#define DT_LOOS 0x60000000 /* Operating system specific range */
+#define DT_VERSYM 0x6ffffff0 /* Symbol versions */
+#define DT_FLAGS_1 0x6ffffffb /* ELF dynamic flags */
+#define DT_VERDEF 0x6ffffffc /* Versions defined by file */
+#define DT_VERDEFNUM 0x6ffffffd /* Number of versions defined by file */
+#define DT_VERNEED 0x6ffffffe /* Versions needed by file */
+#define DT_VERNEEDNUM 0x6fffffff /* Number of versions needed by file */
+#define DT_HIOS 0x6fffffff
+#define DT_LOPROC 0x70000000 /* Processor-specific range */
+#define DT_HIPROC 0x7fffffff
+
+/* Flag values for DT_FLAGS */
+#define DF_ORIGIN 0x00000001 /* uses $ORIGIN */
+#define DF_SYMBOLIC 0x00000002 /* */
+#define DF_TEXTREL 0x00000004 /* */
+#define DF_BIND_NOW 0x00000008 /* */
+#define DF_STATIC_TLS 0x00000010 /* */
+
+/* Flag values for DT_FLAGS_1 */
+#define DF_1_NOW 0x00000001 /* Same as DF_BIND_NOW */
+#define DF_1_GLOBAL 0x00000002 /* Unused */
+#define DF_1_GROUP 0x00000004 /* Is member of group */
+#define DF_1_NODELETE 0x00000008 /* Cannot be deleted from process */
+#define DF_1_LOADFLTR 0x00000010 /* Immediate loading of filters */
+#define DF_1_INITFIRST 0x00000020 /* init/fini takes priority */
+#define DF_1_NOOPEN 0x00000040 /* Do not allow loading on dlopen() */
+#define DF_1_ORIGIN 0x00000080 /* Require $ORIGIN processing */
+#define DF_1_DIRECT 0x00000100 /* Enable direct bindings */
+#define DF_1_INTERPOSE 0x00000400 /* Is an interposer */
+#define DF_1_NODEFLIB 0x00000800 /* Ignore default library search path */
+#define DF_1_NODUMP 0x00001000 /* Cannot be dumped with dldump(3C) */
+#define DF_1_CONFALT 0x00002000 /* Configuration alternative */
+#define DF_1_ENDFILTEE 0x00004000 /* Filtee ends filter's search */
+#define DF_1_DISPRELDNE 0x00008000 /* Did displacement relocation */
+#define DF_1_DISPRELPND 0x00010000 /* Pending displacement relocation */
+#define DF_1_NODIRECT 0x00020000 /* Has non-direct bindings */
+#define DF_1_IGNMULDEF 0x00040000 /* Used internally */
+#define DF_1_NOKSYMS 0x00080000 /* Used internally */
+#define DF_1_NOHDR 0x00100000 /* Used internally */
+#define DF_1_EDITED 0x00200000 /* Has been modified since build */
+#define DF_1_NORELOC 0x00400000 /* Used internally */
+#define DF_1_SYMINTPOSE 0x00800000 /* Has individual symbol interposers */
+#define DF_1_GLOBAUDIT 0x01000000 /* Require global auditing */
+#define DF_1_SINGLETON 0x02000000 /* Has singleton symbols */
+#define DF_1_STUB 0x04000000 /* Stub */
+#define DF_1_PIE 0x08000000 /* Position Independent Executable */
+
+#endif
diff --git a/src/seccomp.c b/src/seccomp.c
new file mode 100644
index 0000000..70d9b10
--- /dev/null
+++ b/src/seccomp.c
@@ -0,0 +1,289 @@
+/*
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * libseccomp hooks.
+ */
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: seccomp.c,v 1.24 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+
+#if HAVE_LIBSECCOMP
+#include <seccomp.h> /* libseccomp */
+#include <sys/prctl.h> /* prctl */
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#define DENY_RULE(call) \
+ do \
+ if (seccomp_rule_add (ctx, SCMP_ACT_KILL, SCMP_SYS(call), 0) == -1) \
+ goto out; \
+ while (/*CONSTCOND*/0)
+#define ALLOW_RULE(call) \
+ do \
+ if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(call), 0) == -1) \
+ goto out; \
+ while (/*CONSTCOND*/0)
+
+#define ALLOW_IOCTL_RULE(param) \
+ do \
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, \
+ SCMP_CMP(1, SCMP_CMP_EQ, (scmp_datum_t)param, \
+ (scmp_datum_t)0)) == -1) \
+ goto out; \
+ while (/*CONSTCOND*/0)
+
+static scmp_filter_ctx ctx;
+
+int
+enable_sandbox_basic(void)
+{
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
+ return -1;
+
+ if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1)
+ return -1;
+
+ // initialize the filter
+ ctx = seccomp_init(SCMP_ACT_ALLOW);
+ if (ctx == NULL)
+ return 1;
+
+ DENY_RULE(_sysctl);
+ DENY_RULE(acct);
+ DENY_RULE(add_key);
+ DENY_RULE(adjtimex);
+ DENY_RULE(chroot);
+ DENY_RULE(clock_adjtime);
+ DENY_RULE(create_module);
+ DENY_RULE(delete_module);
+ DENY_RULE(fanotify_init);
+ DENY_RULE(finit_module);
+ DENY_RULE(get_kernel_syms);
+ DENY_RULE(get_mempolicy);
+ DENY_RULE(init_module);
+ DENY_RULE(io_cancel);
+ DENY_RULE(io_destroy);
+ DENY_RULE(io_getevents);
+ DENY_RULE(io_setup);
+ DENY_RULE(io_submit);
+ DENY_RULE(ioperm);
+ DENY_RULE(iopl);
+ DENY_RULE(ioprio_set);
+ DENY_RULE(kcmp);
+#ifdef __NR_kexec_file_load
+ DENY_RULE(kexec_file_load);
+#endif
+ DENY_RULE(kexec_load);
+ DENY_RULE(keyctl);
+ DENY_RULE(lookup_dcookie);
+ DENY_RULE(mbind);
+ DENY_RULE(nfsservctl);
+ DENY_RULE(migrate_pages);
+ DENY_RULE(modify_ldt);
+ DENY_RULE(mount);
+ DENY_RULE(move_pages);
+ DENY_RULE(name_to_handle_at);
+ DENY_RULE(open_by_handle_at);
+ DENY_RULE(perf_event_open);
+ DENY_RULE(pivot_root);
+ DENY_RULE(process_vm_readv);
+ DENY_RULE(process_vm_writev);
+ DENY_RULE(ptrace);
+ DENY_RULE(reboot);
+ DENY_RULE(remap_file_pages);
+ DENY_RULE(request_key);
+ DENY_RULE(set_mempolicy);
+ DENY_RULE(swapoff);
+ DENY_RULE(swapon);
+ DENY_RULE(sysfs);
+ DENY_RULE(syslog);
+ DENY_RULE(tuxcall);
+ DENY_RULE(umount2);
+ DENY_RULE(uselib);
+ DENY_RULE(vmsplice);
+
+ // blocking dangerous syscalls that file should not need
+ DENY_RULE (execve);
+ DENY_RULE (socket);
+ // ...
+
+
+ // applying filter...
+ if (seccomp_load (ctx) == -1)
+ goto out;
+ // free ctx after the filter has been loaded into the kernel
+ seccomp_release(ctx);
+ return 0;
+
+out:
+ seccomp_release(ctx);
+ return -1;
+}
+
+
+int
+enable_sandbox_full(void)
+{
+
+ // prevent child processes from getting more priv e.g. via setuid,
+ // capabilities, ...
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
+ return -1;
+
+ if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1)
+ return -1;
+
+ // initialize the filter
+ ctx = seccomp_init(SCMP_ACT_KILL);
+ if (ctx == NULL)
+ return -1;
+
+ ALLOW_RULE(access);
+ ALLOW_RULE(brk);
+ ALLOW_RULE(close);
+ ALLOW_RULE(dup2);
+ ALLOW_RULE(exit);
+ ALLOW_RULE(exit_group);
+#ifdef __NR_faccessat
+ ALLOW_RULE(faccessat);
+#endif
+ ALLOW_RULE(fcntl);
+ ALLOW_RULE(fcntl64);
+#ifdef __NR_fstat
+ ALLOW_RULE(fstat);
+#endif
+ ALLOW_RULE(fstat64);
+#ifdef __NR_fstatat64
+ ALLOW_RULE(fstatat64);
+#endif
+ ALLOW_RULE(futex);
+ ALLOW_RULE(getdents);
+#ifdef __NR_getdents64
+ ALLOW_RULE(getdents64);
+#endif
+#ifdef FIONREAD
+ // called in src/compress.c under sread
+ ALLOW_IOCTL_RULE(FIONREAD);
+#endif
+#ifdef TIOCGWINSZ
+ // musl libc may call ioctl TIOCGWINSZ on stdout
+ ALLOW_IOCTL_RULE(TIOCGWINSZ);
+#endif
+#ifdef TCGETS
+ // glibc may call ioctl TCGETS on stdout on physical terminal
+ ALLOW_IOCTL_RULE(TCGETS);
+#endif
+ ALLOW_RULE(lseek);
+ ALLOW_RULE(_llseek);
+ ALLOW_RULE(lstat);
+ ALLOW_RULE(lstat64);
+ ALLOW_RULE(madvise);
+ ALLOW_RULE(mmap);
+ ALLOW_RULE(mmap2);
+ ALLOW_RULE(mprotect);
+ ALLOW_RULE(mremap);
+ ALLOW_RULE(munmap);
+#ifdef __NR_newfstatat
+ ALLOW_RULE(newfstatat);
+#endif
+ ALLOW_RULE(open);
+ ALLOW_RULE(openat);
+ ALLOW_RULE(pread64);
+ ALLOW_RULE(read);
+ ALLOW_RULE(readlink);
+#ifdef __NR_readlinkat
+ ALLOW_RULE(readlinkat);
+#endif
+ ALLOW_RULE(rt_sigaction);
+ ALLOW_RULE(rt_sigprocmask);
+ ALLOW_RULE(rt_sigreturn);
+ ALLOW_RULE(select);
+ ALLOW_RULE(stat);
+ ALLOW_RULE(statx);
+ ALLOW_RULE(stat64);
+ ALLOW_RULE(sysinfo);
+ ALLOW_RULE(umask); // Used in file_pipe2file()
+ ALLOW_RULE(getpid); // Used by glibc in file_pipe2file()
+ ALLOW_RULE(unlink);
+ ALLOW_RULE(write);
+ ALLOW_RULE(writev);
+
+
+#if 0
+ // needed by valgrind
+ ALLOW_RULE(gettid);
+ ALLOW_RULE(rt_sigtimedwait);
+#endif
+
+#if 0
+ /* special restrictions for socket, only allow AF_UNIX/AF_LOCAL */
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
+ SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX)) == -1)
+ goto out;
+
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
+ SCMP_CMP(0, SCMP_CMP_EQ, AF_LOCAL)) == -1)
+ goto out;
+
+
+ /* special restrictions for open, prevent opening files for writing */
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1,
+ SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) == -1)
+ goto out;
+
+ if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1,
+ SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)) == -1)
+ goto out;
+
+ if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1,
+ SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)) == -1)
+ goto out;
+
+
+ /* allow stderr */
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
+ SCMP_CMP(0, SCMP_CMP_EQ, 2)) == -1)
+ goto out;
+#endif
+
+ // applying filter...
+ if (seccomp_load(ctx) == -1)
+ goto out;
+ // free ctx after the filter has been loaded into the kernel
+ seccomp_release(ctx);
+ return 0;
+
+out:
+ // something went wrong
+ seccomp_release(ctx);
+ return -1;
+}
+#endif
diff --git a/src/softmagic.c b/src/softmagic.c
new file mode 100644
index 0000000..e0e839f
--- /dev/null
+++ b/src/softmagic.c
@@ -0,0 +1,2506 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * softmagic - interpret variable magic from MAGIC
+ */
+
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: softmagic.c,v 1.339 2022/12/26 17:31:14 christos Exp $")
+#endif /* lint */
+
+#include "magic.h"
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include "der.h"
+
+file_private int match(struct magic_set *, struct magic *, file_regex_t **, size_t,
+ const struct buffer *, size_t, int, int, int, uint16_t *,
+ uint16_t *, int *, int *, int *, int *, int *);
+file_private int mget(struct magic_set *, struct magic *, const struct buffer *,
+ const unsigned char *, size_t,
+ size_t, unsigned int, int, int, int, uint16_t *,
+ uint16_t *, int *, int *, int *, int *, int *);
+file_private int msetoffset(struct magic_set *, struct magic *, struct buffer *,
+ const struct buffer *, size_t, unsigned int);
+file_private int magiccheck(struct magic_set *, struct magic *, file_regex_t **);
+file_private int mprint(struct magic_set *, struct magic *);
+file_private int moffset(struct magic_set *, struct magic *, const struct buffer *,
+ int32_t *);
+file_private void mdebug(uint32_t, const char *, size_t);
+file_private int mcopy(struct magic_set *, union VALUETYPE *, int, int,
+ const unsigned char *, uint32_t, size_t, struct magic *);
+file_private int mconvert(struct magic_set *, struct magic *, int);
+file_private int print_sep(struct magic_set *, int);
+file_private int handle_annotation(struct magic_set *, struct magic *, int);
+file_private int cvt_8(union VALUETYPE *, const struct magic *);
+file_private int cvt_16(union VALUETYPE *, const struct magic *);
+file_private int cvt_32(union VALUETYPE *, const struct magic *);
+file_private int cvt_64(union VALUETYPE *, const struct magic *);
+
+#define OFFSET_OOB(n, o, i) ((n) < CAST(uint32_t, (o)) || (i) > ((n) - (o)))
+#define BE64(p) ( \
+ (CAST(uint64_t, (p)->hq[0])<<56)| \
+ (CAST(uint64_t, (p)->hq[1])<<48)| \
+ (CAST(uint64_t, (p)->hq[2])<<40)| \
+ (CAST(uint64_t, (p)->hq[3])<<32)| \
+ (CAST(uint64_t, (p)->hq[4])<<24)| \
+ (CAST(uint64_t, (p)->hq[5])<<16)| \
+ (CAST(uint64_t, (p)->hq[6])<<8)| \
+ (CAST(uint64_t, (p)->hq[7])))
+#define LE64(p) ( \
+ (CAST(uint64_t, (p)->hq[7])<<56)| \
+ (CAST(uint64_t, (p)->hq[6])<<48)| \
+ (CAST(uint64_t, (p)->hq[5])<<40)| \
+ (CAST(uint64_t, (p)->hq[4])<<32)| \
+ (CAST(uint64_t, (p)->hq[3])<<24)| \
+ (CAST(uint64_t, (p)->hq[2])<<16)| \
+ (CAST(uint64_t, (p)->hq[1])<<8)| \
+ (CAST(uint64_t, (p)->hq[0])))
+#define LE32(p) ( \
+ (CAST(uint32_t, (p)->hl[3])<<24)| \
+ (CAST(uint32_t, (p)->hl[2])<<16)| \
+ (CAST(uint32_t, (p)->hl[1])<<8)| \
+ (CAST(uint32_t, (p)->hl[0])))
+#define BE32(p) ( \
+ (CAST(uint32_t, (p)->hl[0])<<24)| \
+ (CAST(uint32_t, (p)->hl[1])<<16)| \
+ (CAST(uint32_t, (p)->hl[2])<<8)| \
+ (CAST(uint32_t, (p)->hl[3])))
+#define ME32(p) ( \
+ (CAST(uint32_t, (p)->hl[1])<<24)| \
+ (CAST(uint32_t, (p)->hl[0])<<16)| \
+ (CAST(uint32_t, (p)->hl[3])<<8)| \
+ (CAST(uint32_t, (p)->hl[2])))
+
+#define BE16(p) ((CAST(uint16_t, (p)->hs[0])<<8)|(CAST(uint16_t, (p)->hs[1])))
+#define LE16(p) ((CAST(uint16_t, (p)->hs[1])<<8)|(CAST(uint16_t, (p)->hs[0])))
+#define SEXT(s,v,p) ((s) ? \
+ CAST(intmax_t, CAST(int##v##_t, p)) : \
+ CAST(intmax_t, CAST(uint##v##_t, p)))
+
+/*
+ * softmagic - lookup one file in parsed, in-memory copy of database
+ * Passed the name and FILE * of one file to be typed.
+ */
+/*ARGSUSED1*/ /* nbytes passed for regularity, maybe need later */
+file_protected int
+file_softmagic(struct magic_set *ms, const struct buffer *b,
+ uint16_t *indir_count, uint16_t *name_count, int mode, int text)
+{
+ struct mlist *ml;
+ int rv = 0, printed_something = 0, need_separator = 0, firstline = 1;
+ uint16_t nc, ic;
+
+ if (name_count == NULL) {
+ nc = 0;
+ name_count = &nc;
+ }
+ if (indir_count == NULL) {
+ ic = 0;
+ indir_count = &ic;
+ }
+
+ for (ml = ms->mlist[0]->next; ml != ms->mlist[0]; ml = ml->next) {
+ int ret = match(ms, ml->magic, ml->magic_rxcomp, ml->nmagic, b,
+ 0, mode, text, 0, indir_count, name_count,
+ &printed_something, &need_separator, &firstline,
+ NULL, NULL);
+ switch (ret) {
+ case -1:
+ return ret;
+ case 0:
+ continue;
+ default:
+ if ((ms->flags & MAGIC_CONTINUE) == 0)
+ return ret;
+ rv = ret;
+ break;
+ }
+ }
+
+ return rv;
+}
+
+#define FILE_FMTDEBUG
+#ifdef FILE_FMTDEBUG
+#define F(a, b, c) file_fmtcheck((a), (b), (c), __FILE__, __LINE__)
+
+file_private const char * __attribute__((__format_arg__(3)))
+file_fmtcheck(struct magic_set *ms, const char *desc, const char *def,
+ const char *file, size_t line)
+{
+ const char *ptr;
+
+ if (strchr(desc, '%') == NULL)
+ return desc;
+
+ ptr = fmtcheck(desc, def);
+ if (ptr == def)
+ file_magerror(ms,
+ "%s, %" SIZE_T_FORMAT "u: format `%s' does not match"
+ " with `%s'", file, line, desc, def);
+ return ptr;
+}
+#else
+#define F(a, b, c) fmtcheck((b), (c))
+#endif
+
+/*
+ * Go through the whole list, stopping if you find a match. Process all
+ * the continuations of that match before returning.
+ *
+ * We support multi-level continuations:
+ *
+ * At any time when processing a successful top-level match, there is a
+ * current continuation level; it represents the level of the last
+ * successfully matched continuation.
+ *
+ * Continuations above that level are skipped as, if we see one, it
+ * means that the continuation that controls them - i.e, the
+ * lower-level continuation preceding them - failed to match.
+ *
+ * Continuations below that level are processed as, if we see one,
+ * it means we've finished processing or skipping higher-level
+ * continuations under the control of a successful or unsuccessful
+ * lower-level continuation, and are now seeing the next lower-level
+ * continuation and should process it. The current continuation
+ * level reverts to the level of the one we're seeing.
+ *
+ * Continuations at the current level are processed as, if we see
+ * one, there's no lower-level continuation that may have failed.
+ *
+ * If a continuation matches, we bump the current continuation level
+ * so that higher-level continuations are processed.
+ */
+file_private int
+match(struct magic_set *ms, struct magic *magic, file_regex_t **magic_rxcomp,
+ size_t nmagic, const struct buffer *b, size_t offset, int mode, int text,
+ int flip, uint16_t *indir_count, uint16_t *name_count,
+ int *printed_something, int *need_separator, int *firstline,
+ int *returnval, int *found_match)
+{
+ uint32_t magindex = 0;
+ unsigned int cont_level = 0;
+ int found_matchv = 0; /* if a match is found it is set to 1*/
+ int returnvalv = 0, e;
+ struct buffer bb;
+ int print = (ms->flags & MAGIC_NODESC) == 0;
+
+ /*
+ * returnval can be 0 if a match is found, but there was no
+ * annotation to be printed.
+ */
+ if (returnval == NULL)
+ returnval = &returnvalv;
+ if (found_match == NULL)
+ found_match = &found_matchv;
+
+ if (file_check_mem(ms, cont_level) == -1)
+ return -1;
+
+ for (magindex = 0; magindex < nmagic; magindex++) {
+ int flush = 0;
+ struct magic *m = &magic[magindex];
+ file_regex_t **m_rxcomp = &magic_rxcomp[magindex];
+
+ if (m->type != FILE_NAME)
+ if ((IS_STRING(m->type) &&
+#define FLT (STRING_BINTEST | STRING_TEXTTEST)
+ ((text && (m->str_flags & FLT) == STRING_BINTEST) ||
+ (!text && (m->str_flags & FLT) == STRING_TEXTTEST))) ||
+ (m->flag & mode) != mode) {
+flush:
+ /* Skip sub-tests */
+ while (magindex < nmagic - 1 &&
+ magic[magindex + 1].cont_level != 0)
+ magindex++;
+ cont_level = 0;
+ continue; /* Skip to next top-level test*/
+ }
+
+ if (msetoffset(ms, m, &bb, b, offset, cont_level) == -1)
+ goto flush;
+ ms->line = m->lineno;
+
+ /* if main entry matches, print it... */
+ switch (mget(ms, m, b, CAST(const unsigned char *, bb.fbuf),
+ bb.flen, offset, cont_level,
+ mode, text, flip, indir_count, name_count,
+ printed_something, need_separator, firstline, returnval,
+ found_match))
+ {
+ case -1:
+ return -1;
+ case 0:
+ flush = m->reln != '!';
+ break;
+ default:
+ if (m->type == FILE_INDIRECT) {
+ *found_match = 1;
+ *returnval = 1;
+ }
+
+ switch (magiccheck(ms, m, m_rxcomp)) {
+ case -1:
+ return -1;
+ case 0:
+ flush++;
+ break;
+ default:
+ flush = 0;
+ break;
+ }
+ break;
+ }
+ if (flush) {
+ /*
+ * main entry didn't match,
+ * flush its continuations
+ */
+ goto flush;
+ }
+
+ if ((e = handle_annotation(ms, m, *firstline)) != 0)
+ {
+ *found_match = 1;
+ *need_separator = 1;
+ *printed_something = 1;
+ *returnval = 1;
+ return e;
+ }
+
+ /*
+ * If we are going to print something, we'll need to print
+ * a blank before we print something else.
+ */
+ if (*m->desc) {
+ *found_match = 1;
+ if (print) {
+ *returnval = 1;
+ *need_separator = 1;
+ *printed_something = 1;
+ if (print_sep(ms, *firstline) == -1)
+ return -1;
+ if (mprint(ms, m) == -1)
+ return -1;
+ }
+ }
+
+ switch (moffset(ms, m, &bb, &ms->c.li[cont_level].off)) {
+ case -1:
+ case 0:
+ goto flush;
+ default:
+ break;
+ }
+
+ /* and any continuations that match */
+ if (file_check_mem(ms, ++cont_level) == -1)
+ return -1;
+
+ while (magindex + 1 < nmagic &&
+ magic[magindex + 1].cont_level != 0) {
+ m = &magic[++magindex];
+ m_rxcomp = &magic_rxcomp[magindex];
+ ms->line = m->lineno; /* for messages */
+
+ if (cont_level < m->cont_level)
+ continue;
+ if (cont_level > m->cont_level) {
+ /*
+ * We're at the end of the level
+ * "cont_level" continuations.
+ */
+ cont_level = m->cont_level;
+ }
+ if (msetoffset(ms, m, &bb, b, offset, cont_level) == -1)
+ goto flush;
+ if (m->flag & OFFADD) {
+ if (cont_level == 0) {
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr,
+ "direct *zero*"
+ " cont_level\n");
+ return 0;
+ }
+ ms->offset +=
+ ms->c.li[cont_level - 1].off;
+ }
+
+#ifdef ENABLE_CONDITIONALS
+ if (m->cond == COND_ELSE ||
+ m->cond == COND_ELIF) {
+ if (ms->c.li[cont_level].last_match == 1)
+ continue;
+ }
+#endif
+ switch (mget(ms, m, b, CAST(const unsigned char *,
+ bb.fbuf), bb.flen, offset,
+ cont_level, mode, text, flip, indir_count,
+ name_count, printed_something, need_separator,
+ firstline, returnval, found_match)) {
+ case -1:
+ return -1;
+ case 0:
+ if (m->reln != '!')
+ continue;
+ flush = 1;
+ break;
+ default:
+ if (m->type == FILE_INDIRECT) {
+ *found_match = 1;
+ *returnval = 1;
+ }
+ flush = 0;
+ break;
+ }
+
+ switch (flush ? 1 : magiccheck(ms, m, m_rxcomp)) {
+ case -1:
+ return -1;
+ case 0:
+#ifdef ENABLE_CONDITIONALS
+ ms->c.li[cont_level].last_match = 0;
+#endif
+ break;
+ default:
+#ifdef ENABLE_CONDITIONALS
+ ms->c.li[cont_level].last_match = 1;
+#endif
+ if (m->type == FILE_CLEAR)
+ ms->c.li[cont_level].got_match = 0;
+ else if (ms->c.li[cont_level].got_match) {
+ if (m->type == FILE_DEFAULT)
+ break;
+ } else
+ ms->c.li[cont_level].got_match = 1;
+
+ if ((e = handle_annotation(ms, m, *firstline))
+ != 0) {
+ *found_match = 1;
+ *need_separator = 1;
+ *printed_something = 1;
+ *returnval = 1;
+ return e;
+ }
+ if (*m->desc) {
+ *found_match = 1;
+ }
+ if (print && *m->desc) {
+ *returnval = 1;
+ /*
+ * This continuation matched. Print
+ * its message, with a blank before it
+ * if the previous item printed and
+ * this item isn't empty.
+ */
+ /*
+ * If we are going to print something,
+ * make sure that we have a separator
+ * first.
+ */
+ if (!*printed_something) {
+ *printed_something = 1;
+ if (print_sep(ms, *firstline)
+ == -1)
+ return -1;
+ }
+ /* space if previous printed */
+ if (*need_separator
+ && (m->flag & NOSPACE) == 0) {
+ if (file_printf(ms, " ") == -1)
+ return -1;
+ }
+ if (mprint(ms, m) == -1)
+ return -1;
+ *need_separator = 1;
+ }
+
+ switch (moffset(ms, m, &bb,
+ &ms->c.li[cont_level].off)) {
+ case -1:
+ case 0:
+ cont_level--;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If we see any continuations
+ * at a higher level,
+ * process them.
+ */
+ if (file_check_mem(ms, ++cont_level) == -1)
+ return -1;
+ break;
+ }
+ }
+ if (*printed_something) {
+ *firstline = 0;
+ }
+ if (*found_match) {
+ if ((ms->flags & MAGIC_CONTINUE) == 0)
+ return *returnval;
+ // So that we print a separator
+ *printed_something = 0;
+ *firstline = 0;
+ }
+ cont_level = 0;
+ }
+ return *returnval;
+}
+
+file_private int
+check_fmt(struct magic_set *ms, const char *fmt)
+{
+ file_regex_t rx;
+ int rc, rv = -1;
+ const char* pat = "%[-0-9\\.]*s";
+
+ if (strchr(fmt, '%') == NULL)
+ return 0;
+
+ rc = file_regcomp(ms, &rx, pat, REG_EXTENDED|REG_NOSUB);
+ if (rc == 0) {
+ rc = file_regexec(ms, &rx, fmt, 0, 0, 0);
+ rv = !rc;
+ }
+ file_regfree(&rx);
+ return rv;
+}
+
+#if !defined(HAVE_STRNDUP) || defined(__aiws__) || defined(_AIX)
+# if defined(__aiws__) || defined(_AIX)
+# define strndup aix_strndup /* aix is broken */
+# endif
+char *strndup(const char *, size_t);
+
+char *
+strndup(const char *str, size_t n)
+{
+ size_t len;
+ char *copy;
+
+ for (len = 0; len < n && str[len]; len++)
+ continue;
+ if ((copy = CAST(char *, malloc(len + 1))) == NULL)
+ return NULL;
+ (void)memcpy(copy, str, len);
+ copy[len] = '\0';
+ return copy;
+}
+#endif /* HAVE_STRNDUP */
+
+static int
+varexpand(struct magic_set *ms, char *buf, size_t len, const char *str)
+{
+ const char *ptr, *sptr, *e, *t, *ee, *et;
+ size_t l;
+
+ for (sptr = str; (ptr = strstr(sptr, "${")) != NULL;) {
+ l = CAST(size_t, ptr - sptr);
+ if (l >= len)
+ return -1;
+ memcpy(buf, sptr, l);
+ buf += l;
+ len -= l;
+ ptr += 2;
+ if (!*ptr || ptr[1] != '?')
+ return -1;
+ for (et = t = ptr + 2; *et && *et != ':'; et++)
+ continue;
+ if (*et != ':')
+ return -1;
+ for (ee = e = et + 1; *ee && *ee != '}'; ee++)
+ continue;
+ if (*ee != '}')
+ return -1;
+ switch (*ptr) {
+ case 'x':
+ if (ms->mode & 0111) {
+ ptr = t;
+ l = et - t;
+ } else {
+ ptr = e;
+ l = ee - e;
+ }
+ break;
+ default:
+ return -1;
+ }
+ if (l >= len)
+ return -1;
+ memcpy(buf, ptr, l);
+ buf += l;
+ len -= l;
+ sptr = ee + 1;
+ }
+
+ l = strlen(sptr);
+ if (l >= len)
+ return -1;
+
+ memcpy(buf, sptr, l);
+ buf[l] = '\0';
+ return 0;
+}
+
+
+file_private int
+mprint(struct magic_set *ms, struct magic *m)
+{
+ uint64_t v;
+ float vf;
+ double vd;
+ char buf[128], tbuf[26], sbuf[512], ebuf[512];
+ const char *desc;
+ union VALUETYPE *p = &ms->ms_value;
+
+ if (varexpand(ms, ebuf, sizeof(ebuf), m->desc) == -1)
+ desc = m->desc;
+ else
+ desc = ebuf;
+
+#define PRINTER(value, format, stype, utype) \
+ v = file_signextend(ms, m, CAST(uint64_t, value)); \
+ switch (check_fmt(ms, desc)) { \
+ case -1: \
+ return -1; \
+ case 1: \
+ if (m->flag & UNSIGNED) { \
+ (void)snprintf(buf, sizeof(buf), "%" format "u", \
+ CAST(utype, v)); \
+ } else { \
+ (void)snprintf(buf, sizeof(buf), "%" format "d", \
+ CAST(stype, v)); \
+ } \
+ if (file_printf(ms, F(ms, desc, "%s"), buf) == -1) \
+ return -1; \
+ break; \
+ default: \
+ if (m->flag & UNSIGNED) { \
+ if (file_printf(ms, F(ms, desc, "%" format "u"), \
+ CAST(utype, v)) == -1) \
+ return -1; \
+ } else { \
+ if (file_printf(ms, F(ms, desc, "%" format "d"), \
+ CAST(stype, v)) == -1) \
+ return -1; \
+ } \
+ break; \
+ } \
+ break
+
+ switch (m->type) {
+ case FILE_BYTE:
+ PRINTER(p->b, "", int8_t, uint8_t);
+
+ case FILE_SHORT:
+ case FILE_BESHORT:
+ case FILE_LESHORT:
+ PRINTER(p->h, "", int16_t, uint16_t);
+
+ case FILE_LONG:
+ case FILE_BELONG:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ PRINTER(p->l, "", int32_t, uint32_t);
+
+ case FILE_QUAD:
+ case FILE_BEQUAD:
+ case FILE_LEQUAD:
+ case FILE_OFFSET:
+ PRINTER(p->q, INT64_T_FORMAT, long long, unsigned long long);
+
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ if (m->reln == '=' || m->reln == '!') {
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_printable(ms, sbuf, sizeof(sbuf), m->value.s,
+ sizeof(m->value.s))) == -1)
+ return -1;
+ }
+ else {
+ char *str = p->s;
+
+ /* compute t before we mangle the string? */
+
+ if (*m->value.s == '\0')
+ str[strcspn(str, "\r\n")] = '\0';
+
+ if (m->str_flags & STRING_TRIM)
+ str = file_strtrim(str);
+
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_printable(ms, sbuf, sizeof(sbuf), str,
+ sizeof(p->s) - (str - p->s))) == -1)
+ return -1;
+
+ if (m->type == FILE_PSTRING) {
+ size_t l = file_pstring_length_size(ms, m);
+ if (l == FILE_BADSIZE)
+ return -1;
+ }
+ }
+ break;
+
+ case FILE_DATE:
+ case FILE_BEDATE:
+ case FILE_LEDATE:
+ case FILE_MEDATE:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmtdatetime(tbuf, sizeof(tbuf), p->l, 0)) == -1)
+ return -1;
+ break;
+
+ case FILE_LDATE:
+ case FILE_BELDATE:
+ case FILE_LELDATE:
+ case FILE_MELDATE:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmtdatetime(tbuf, sizeof(tbuf), p->l, FILE_T_LOCAL))
+ == -1)
+ return -1;
+ break;
+
+ case FILE_QDATE:
+ case FILE_BEQDATE:
+ case FILE_LEQDATE:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmtdatetime(tbuf, sizeof(tbuf), p->q, 0)) == -1)
+ return -1;
+ break;
+
+ case FILE_QLDATE:
+ case FILE_BEQLDATE:
+ case FILE_LEQLDATE:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmtdatetime(tbuf, sizeof(tbuf), p->q, FILE_T_LOCAL)) == -1)
+ return -1;
+ break;
+
+ case FILE_QWDATE:
+ case FILE_BEQWDATE:
+ case FILE_LEQWDATE:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmtdatetime(tbuf, sizeof(tbuf), p->q, FILE_T_WINDOWS))
+ == -1)
+ return -1;
+ break;
+
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ vf = p->f;
+ switch (check_fmt(ms, desc)) {
+ case -1:
+ return -1;
+ case 1:
+ (void)snprintf(buf, sizeof(buf), "%g", vf);
+ if (file_printf(ms, F(ms, desc, "%s"), buf) == -1)
+ return -1;
+ break;
+ default:
+ if (file_printf(ms, F(ms, desc, "%g"), vf) == -1)
+ return -1;
+ break;
+ }
+ break;
+
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ vd = p->d;
+ switch (check_fmt(ms, desc)) {
+ case -1:
+ return -1;
+ case 1:
+ (void)snprintf(buf, sizeof(buf), "%g", vd);
+ if (file_printf(ms, F(ms, desc, "%s"), buf) == -1)
+ return -1;
+ break;
+ default:
+ if (file_printf(ms, F(ms, desc, "%g"), vd) == -1)
+ return -1;
+ break;
+ }
+ break;
+
+ case FILE_SEARCH:
+ case FILE_REGEX: {
+ char *cp, *scp;
+ int rval;
+
+ cp = strndup(RCAST(const char *, ms->search.s),
+ ms->search.rm_len);
+ if (cp == NULL) {
+ file_oomem(ms, ms->search.rm_len);
+ return -1;
+ }
+ scp = (m->str_flags & STRING_TRIM) ? file_strtrim(cp) : cp;
+
+ rval = file_printf(ms, F(ms, desc, "%s"), file_printable(ms,
+ sbuf, sizeof(sbuf), scp, ms->search.rm_len));
+ free(cp);
+
+ if (rval == -1)
+ return -1;
+ break;
+ }
+
+ case FILE_DEFAULT:
+ case FILE_CLEAR:
+ if (file_printf(ms, "%s", m->desc) == -1)
+ return -1;
+ break;
+
+ case FILE_INDIRECT:
+ case FILE_USE:
+ case FILE_NAME:
+ break;
+ case FILE_DER:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_printable(ms, sbuf, sizeof(sbuf), ms->ms_value.s,
+ sizeof(ms->ms_value.s))) == -1)
+ return -1;
+ break;
+ case FILE_GUID:
+ (void) file_print_guid(buf, sizeof(buf), ms->ms_value.guid);
+ if (file_printf(ms, F(ms, desc, "%s"), buf) == -1)
+ return -1;
+ break;
+ case FILE_MSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmtdate(tbuf, sizeof(tbuf), p->h)) == -1)
+ return -1;
+ break;
+ case FILE_MSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ if (file_printf(ms, F(ms, desc, "%s"),
+ file_fmttime(tbuf, sizeof(tbuf), p->h)) == -1)
+ return -1;
+ break;
+ case FILE_OCTAL:
+ file_fmtnum(buf, sizeof(buf), m->value.s, 8);
+ if (file_printf(ms, F(ms, desc, "%s"), buf) == -1)
+ return -1;
+ break;
+ default:
+ file_magerror(ms, "invalid m->type (%d) in mprint()", m->type);
+ return -1;
+ }
+ return 0;
+}
+
+file_private int
+moffset(struct magic_set *ms, struct magic *m, const struct buffer *b,
+ int32_t *op)
+{
+ size_t nbytes = b->flen;
+ int32_t o;
+
+ switch (m->type) {
+ case FILE_BYTE:
+ o = CAST(int32_t, (ms->offset + sizeof(char)));
+ break;
+
+ case FILE_SHORT:
+ case FILE_BESHORT:
+ case FILE_LESHORT:
+ case FILE_MSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ o = CAST(int32_t, (ms->offset + sizeof(short)));
+ break;
+
+ case FILE_LONG:
+ case FILE_BELONG:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ o = CAST(int32_t, (ms->offset + sizeof(int32_t)));
+ break;
+
+ case FILE_QUAD:
+ case FILE_BEQUAD:
+ case FILE_LEQUAD:
+ o = CAST(int32_t, (ms->offset + sizeof(int64_t)));
+ break;
+
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ case FILE_OCTAL:
+ if (m->reln == '=' || m->reln == '!') {
+ o = ms->offset + m->vallen;
+ } else {
+ union VALUETYPE *p = &ms->ms_value;
+
+ if (*m->value.s == '\0')
+ p->s[strcspn(p->s, "\r\n")] = '\0';
+ o = CAST(uint32_t, (ms->offset + strlen(p->s)));
+ if (m->type == FILE_PSTRING) {
+ size_t l = file_pstring_length_size(ms, m);
+ if (l == FILE_BADSIZE)
+ return -1;
+ o += CAST(uint32_t, l);
+ }
+ }
+ break;
+
+ case FILE_DATE:
+ case FILE_BEDATE:
+ case FILE_LEDATE:
+ case FILE_MEDATE:
+ o = CAST(int32_t, (ms->offset + sizeof(uint32_t)));
+ break;
+
+ case FILE_LDATE:
+ case FILE_BELDATE:
+ case FILE_LELDATE:
+ case FILE_MELDATE:
+ o = CAST(int32_t, (ms->offset + sizeof(uint32_t)));
+ break;
+
+ case FILE_QDATE:
+ case FILE_BEQDATE:
+ case FILE_LEQDATE:
+ o = CAST(int32_t, (ms->offset + sizeof(uint64_t)));
+ break;
+
+ case FILE_QLDATE:
+ case FILE_BEQLDATE:
+ case FILE_LEQLDATE:
+ o = CAST(int32_t, (ms->offset + sizeof(uint64_t)));
+ break;
+
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ o = CAST(int32_t, (ms->offset + sizeof(float)));
+ break;
+
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ o = CAST(int32_t, (ms->offset + sizeof(double)));
+ break;
+
+ case FILE_REGEX:
+ if ((m->str_flags & REGEX_OFFSET_START) != 0)
+ o = CAST(int32_t, ms->search.offset);
+ else
+ o = CAST(int32_t,
+ (ms->search.offset + ms->search.rm_len));
+ break;
+
+ case FILE_SEARCH:
+ if ((m->str_flags & REGEX_OFFSET_START) != 0)
+ o = CAST(int32_t, ms->search.offset);
+ else
+ o = CAST(int32_t, (ms->search.offset + m->vallen));
+ break;
+
+ case FILE_CLEAR:
+ case FILE_DEFAULT:
+ case FILE_INDIRECT:
+ case FILE_OFFSET:
+ case FILE_USE:
+ o = ms->offset;
+ break;
+
+ case FILE_DER:
+ o = der_offs(ms, m, nbytes);
+ if (o == -1 || CAST(size_t, o) > nbytes) {
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ (void)fprintf(stderr,
+ "Bad DER offset %d nbytes=%"
+ SIZE_T_FORMAT "u", o, nbytes);
+ }
+ *op = 0;
+ return 0;
+ }
+ break;
+
+ case FILE_GUID:
+ o = CAST(int32_t, (ms->offset + 2 * sizeof(uint64_t)));
+ break;
+
+ default:
+ o = 0;
+ break;
+ }
+
+ if (CAST(size_t, o) > nbytes) {
+#if 0
+ file_error(ms, 0, "Offset out of range %" SIZE_T_FORMAT
+ "u > %" SIZE_T_FORMAT "u", (size_t)o, nbytes);
+#endif
+ return -1;
+ }
+ *op = o;
+ return 1;
+}
+
+file_private uint32_t
+cvt_id3(struct magic_set *ms, uint32_t v)
+{
+ v = ((((v >> 0) & 0x7f) << 0) |
+ (((v >> 8) & 0x7f) << 7) |
+ (((v >> 16) & 0x7f) << 14) |
+ (((v >> 24) & 0x7f) << 21));
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "id3 offs=%u\n", v);
+ return v;
+}
+
+file_private int
+cvt_flip(int type, int flip)
+{
+ if (flip == 0)
+ return type;
+ switch (type) {
+ case FILE_BESHORT:
+ return FILE_LESHORT;
+ case FILE_BELONG:
+ return FILE_LELONG;
+ case FILE_BEDATE:
+ return FILE_LEDATE;
+ case FILE_BELDATE:
+ return FILE_LELDATE;
+ case FILE_BEQUAD:
+ return FILE_LEQUAD;
+ case FILE_BEQDATE:
+ return FILE_LEQDATE;
+ case FILE_BEQLDATE:
+ return FILE_LEQLDATE;
+ case FILE_BEQWDATE:
+ return FILE_LEQWDATE;
+ case FILE_LESHORT:
+ return FILE_BESHORT;
+ case FILE_LELONG:
+ return FILE_BELONG;
+ case FILE_LEDATE:
+ return FILE_BEDATE;
+ case FILE_LELDATE:
+ return FILE_BELDATE;
+ case FILE_LEQUAD:
+ return FILE_BEQUAD;
+ case FILE_LEQDATE:
+ return FILE_BEQDATE;
+ case FILE_LEQLDATE:
+ return FILE_BEQLDATE;
+ case FILE_LEQWDATE:
+ return FILE_BEQWDATE;
+ case FILE_BEFLOAT:
+ return FILE_LEFLOAT;
+ case FILE_LEFLOAT:
+ return FILE_BEFLOAT;
+ case FILE_BEDOUBLE:
+ return FILE_LEDOUBLE;
+ case FILE_LEDOUBLE:
+ return FILE_BEDOUBLE;
+ default:
+ return type;
+ }
+}
+#define DO_CVT(fld, type) \
+ if (m->num_mask) \
+ switch (m->mask_op & FILE_OPS_MASK) { \
+ case FILE_OPAND: \
+ p->fld &= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPOR: \
+ p->fld |= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPXOR: \
+ p->fld ^= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPADD: \
+ p->fld += CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPMINUS: \
+ p->fld -= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPMULTIPLY: \
+ p->fld *= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPDIVIDE: \
+ if (CAST(type, m->num_mask) == 0) \
+ return -1; \
+ p->fld /= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPMODULO: \
+ if (CAST(type, m->num_mask) == 0) \
+ return -1; \
+ p->fld %= CAST(type, m->num_mask); \
+ break; \
+ } \
+ if (m->mask_op & FILE_OPINVERSE) \
+ p->fld = ~p->fld \
+
+file_private int
+cvt_8(union VALUETYPE *p, const struct magic *m)
+{
+ DO_CVT(b, uint8_t);
+ return 0;
+}
+
+file_private int
+cvt_16(union VALUETYPE *p, const struct magic *m)
+{
+ DO_CVT(h, uint16_t);
+ return 0;
+}
+
+file_private int
+cvt_32(union VALUETYPE *p, const struct magic *m)
+{
+ DO_CVT(l, uint32_t);
+ return 0;
+}
+
+file_private int
+cvt_64(union VALUETYPE *p, const struct magic *m)
+{
+ DO_CVT(q, uint64_t);
+ return 0;
+}
+
+#define DO_CVT2(fld, type) \
+ if (m->num_mask) \
+ switch (m->mask_op & FILE_OPS_MASK) { \
+ case FILE_OPADD: \
+ p->fld += CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPMINUS: \
+ p->fld -= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPMULTIPLY: \
+ p->fld *= CAST(type, m->num_mask); \
+ break; \
+ case FILE_OPDIVIDE: \
+ if (CAST(type, m->num_mask) == 0) \
+ return -1; \
+ p->fld /= CAST(type, m->num_mask); \
+ break; \
+ } \
+
+file_private int
+cvt_float(union VALUETYPE *p, const struct magic *m)
+{
+ DO_CVT2(f, float);
+ return 0;
+}
+
+file_private int
+cvt_double(union VALUETYPE *p, const struct magic *m)
+{
+ DO_CVT2(d, double);
+ return 0;
+}
+
+/*
+ * Convert the byte order of the data we are looking at
+ * While we're here, let's apply the mask operation
+ * (unless you have a better idea)
+ */
+file_private int
+mconvert(struct magic_set *ms, struct magic *m, int flip)
+{
+ union VALUETYPE *p = &ms->ms_value;
+
+ switch (cvt_flip(m->type, flip)) {
+ case FILE_BYTE:
+ if (cvt_8(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_SHORT:
+ case FILE_MSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ if (cvt_16(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_LONG:
+ case FILE_DATE:
+ case FILE_LDATE:
+ if (cvt_32(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_QUAD:
+ case FILE_QDATE:
+ case FILE_QLDATE:
+ case FILE_QWDATE:
+ case FILE_OFFSET:
+ if (cvt_64(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_STRING:
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ case FILE_OCTAL: {
+ /* Null terminate and eat *trailing* return */
+ p->s[sizeof(p->s) - 1] = '\0';
+ return 1;
+ }
+ case FILE_PSTRING: {
+ char *ptr1, *ptr2;
+ size_t len, sz = file_pstring_length_size(ms, m);
+ if (sz == FILE_BADSIZE)
+ return 0;
+ ptr1 = p->s;
+ ptr2 = ptr1 + sz;
+ len = file_pstring_get_length(ms, m, ptr1);
+ if (len == FILE_BADSIZE)
+ return 0;
+ sz = sizeof(p->s) - sz; /* maximum length of string */
+ if (len >= sz) {
+ /*
+ * The size of the pascal string length (sz)
+ * is 1, 2, or 4. We need at least 1 byte for NUL
+ * termination, but we've already truncated the
+ * string by p->s, so we need to deduct sz.
+ * Because we can use one of the bytes of the length
+ * after we shifted as NUL termination.
+ */
+ len = sz;
+ }
+ while (len--)
+ *ptr1++ = *ptr2++;
+ *ptr1 = '\0';
+ return 1;
+ }
+ case FILE_BESHORT:
+ p->h = CAST(short, BE16(p));
+ if (cvt_16(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_BELONG:
+ case FILE_BEDATE:
+ case FILE_BELDATE:
+ p->l = CAST(int32_t, BE32(p));
+ if (cvt_32(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_BEQUAD:
+ case FILE_BEQDATE:
+ case FILE_BEQLDATE:
+ case FILE_BEQWDATE:
+ p->q = CAST(uint64_t, BE64(p));
+ if (cvt_64(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_LESHORT:
+ p->h = CAST(short, LE16(p));
+ if (cvt_16(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_LELONG:
+ case FILE_LEDATE:
+ case FILE_LELDATE:
+ p->l = CAST(int32_t, LE32(p));
+ if (cvt_32(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_LEQUAD:
+ case FILE_LEQDATE:
+ case FILE_LEQLDATE:
+ case FILE_LEQWDATE:
+ p->q = CAST(uint64_t, LE64(p));
+ if (cvt_64(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_MELONG:
+ case FILE_MEDATE:
+ case FILE_MELDATE:
+ p->l = CAST(int32_t, ME32(p));
+ if (cvt_32(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_FLOAT:
+ if (cvt_float(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_BEFLOAT:
+ p->l = BE32(p);
+ if (cvt_float(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_LEFLOAT:
+ p->l = LE32(p);
+ if (cvt_float(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_DOUBLE:
+ if (cvt_double(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_BEDOUBLE:
+ p->q = BE64(p);
+ if (cvt_double(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_LEDOUBLE:
+ p->q = LE64(p);
+ if (cvt_double(p, m) == -1)
+ goto out;
+ return 1;
+ case FILE_REGEX:
+ case FILE_SEARCH:
+ case FILE_DEFAULT:
+ case FILE_CLEAR:
+ case FILE_NAME:
+ case FILE_USE:
+ case FILE_DER:
+ case FILE_GUID:
+ return 1;
+ default:
+ file_magerror(ms, "invalid type %d in mconvert()", m->type);
+ return 0;
+ }
+out:
+ file_magerror(ms, "zerodivide in mconvert()");
+ return 0;
+}
+
+
+file_private void
+mdebug(uint32_t offset, const char *str, size_t len)
+{
+ (void) fprintf(stderr, "mget/%" SIZE_T_FORMAT "u @%d: ", len, offset);
+ file_showstr(stderr, str, len);
+ (void) fputc('\n', stderr);
+ (void) fputc('\n', stderr);
+}
+
+file_private int
+mcopy(struct magic_set *ms, union VALUETYPE *p, int type, int indir,
+ const unsigned char *s, uint32_t offset, size_t nbytes, struct magic *m)
+{
+ size_t size = sizeof(*p);
+ /*
+ * Note: FILE_SEARCH and FILE_REGEX do not actually copy
+ * anything, but setup pointers into the source
+ */
+ if (indir == 0) {
+ switch (type) {
+ case FILE_DER:
+ case FILE_SEARCH:
+ if (offset > nbytes)
+ offset = CAST(uint32_t, nbytes);
+ ms->search.s = RCAST(const char *, s) + offset;
+ ms->search.s_len = nbytes - offset;
+ ms->search.offset = offset;
+ return 0;
+
+ case FILE_REGEX: {
+ const char *b;
+ const char *c;
+ const char *last; /* end of search region */
+ const char *buf; /* start of search region */
+ const char *end;
+ size_t lines, linecnt, bytecnt;
+
+ if (s == NULL || nbytes < offset) {
+ ms->search.s_len = 0;
+ ms->search.s = NULL;
+ return 0;
+ }
+
+ if (m->str_flags & REGEX_LINE_COUNT) {
+ linecnt = m->str_range;
+ bytecnt = linecnt * 80;
+ } else {
+ linecnt = 0;
+ bytecnt = m->str_range;
+ }
+
+ if (bytecnt == 0 || bytecnt > nbytes - offset)
+ bytecnt = nbytes - offset;
+ if (bytecnt > ms->regex_max)
+ bytecnt = ms->regex_max;
+
+ buf = RCAST(const char *, s) + offset;
+ end = last = RCAST(const char *, s) + bytecnt + offset;
+ /* mget() guarantees buf <= last */
+ for (lines = linecnt, b = buf; lines && b < end &&
+ ((b = CAST(const char *,
+ memchr(c = b, '\n', CAST(size_t, (end - b)))))
+ || (b = CAST(const char *,
+ memchr(c, '\r', CAST(size_t, (end - c))))));
+ lines--, b++) {
+ if (b < end - 1 && b[0] == '\r' && b[1] == '\n')
+ b++;
+ if (b < end - 1 && b[0] == '\n')
+ b++;
+ last = b;
+ }
+ if (lines)
+ last = end;
+
+ ms->search.s = buf;
+ ms->search.s_len = last - buf;
+ ms->search.offset = offset;
+ ms->search.rm_len = 0;
+ return 0;
+ }
+ case FILE_BESTRING16:
+ case FILE_LESTRING16: {
+ const unsigned char *src = s + offset;
+ const unsigned char *esrc = s + nbytes;
+ char *dst = p->s;
+ char *edst = &p->s[sizeof(p->s) - 1];
+
+ if (type == FILE_BESTRING16)
+ src++;
+
+ /* check that offset is within range */
+ if (offset >= nbytes)
+ break;
+ for (/*EMPTY*/; src < esrc; src += 2, dst++) {
+ if (dst < edst)
+ *dst = *src;
+ else
+ break;
+ if (*dst == '\0') {
+ if (type == FILE_BESTRING16 ?
+ *(src - 1) != '\0' :
+ ((src + 1 < esrc) &&
+ *(src + 1) != '\0'))
+ *dst = ' ';
+ }
+ }
+ *edst = '\0';
+ return 0;
+ }
+ case FILE_STRING: /* XXX - these two should not need */
+ case FILE_PSTRING: /* to copy anything, but do anyway. */
+ if (m->str_range != 0 && m->str_range < sizeof(*p))
+ size = m->str_range;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (type == FILE_OFFSET) {
+ (void)memset(p, '\0', sizeof(*p));
+ p->q = offset;
+ return 0;
+ }
+
+ if (offset >= nbytes) {
+ (void)memset(p, '\0', sizeof(*p));
+ return 0;
+ }
+ if (nbytes - offset < size)
+ nbytes = nbytes - offset;
+ else
+ nbytes = size;
+
+ (void)memcpy(p, s + offset, nbytes);
+
+ /*
+ * the usefulness of padding with zeroes eludes me, it
+ * might even cause problems
+ */
+ if (nbytes < sizeof(*p))
+ (void)memset(RCAST(char *, RCAST(void *, p)) + nbytes, '\0',
+ sizeof(*p) - nbytes);
+ return 0;
+}
+
+file_private int
+do_ops(struct magic *m, uint32_t *rv, intmax_t lhs, intmax_t off)
+{
+ intmax_t offset;
+ // On purpose not INTMAX_MAX
+ if (lhs >= UINT_MAX || lhs <= INT_MIN ||
+ off >= UINT_MAX || off <= INT_MIN) {
+ fprintf(stderr, "lhs/off overflow %jd %jd\n", lhs, off);
+ return 1;
+ }
+
+ if (off) {
+ switch (m->in_op & FILE_OPS_MASK) {
+ case FILE_OPAND:
+ offset = lhs & off;
+ break;
+ case FILE_OPOR:
+ offset = lhs | off;
+ break;
+ case FILE_OPXOR:
+ offset = lhs ^ off;
+ break;
+ case FILE_OPADD:
+ offset = lhs + off;
+ break;
+ case FILE_OPMINUS:
+ offset = lhs - off;
+ break;
+ case FILE_OPMULTIPLY:
+ offset = lhs * off;
+ break;
+ case FILE_OPDIVIDE:
+ offset = lhs / off;
+ break;
+ case FILE_OPMODULO:
+ offset = lhs % off;
+ break;
+ }
+ } else
+ offset = lhs;
+ if (m->in_op & FILE_OPINVERSE)
+ offset = ~offset;
+ if (offset >= UINT_MAX) {
+ fprintf(stderr, "offset overflow %jd\n", offset);
+ return 1;
+ }
+ *rv = CAST(uint32_t, offset);
+ return 0;
+}
+
+file_private int
+msetoffset(struct magic_set *ms, struct magic *m, struct buffer *bb,
+ const struct buffer *b, size_t o, unsigned int cont_level)
+{
+ int32_t offset;
+ if (m->flag & OFFNEGATIVE) {
+ offset = -m->offset;
+ if (cont_level > 0) {
+ if (m->flag & (OFFADD|INDIROFFADD))
+ goto normal;
+#if 0
+ file_error(ms, 0, "negative offset %d at continuation"
+ "level %u", m->offset, cont_level);
+ return -1;
+#endif
+ }
+ if (buffer_fill(b) == -1)
+ return -1;
+ if (o != 0) {
+ // Not yet!
+ file_magerror(ms, "non zero offset %" SIZE_T_FORMAT
+ "u at level %u", o, cont_level);
+ return -1;
+ }
+ if (CAST(size_t, m->offset) > b->elen)
+ return -1;
+ buffer_init(bb, -1, NULL, b->ebuf, b->elen);
+ ms->eoffset = ms->offset = CAST(int32_t, b->elen - m->offset);
+ } else {
+ offset = m->offset;
+ if (cont_level == 0) {
+normal:
+ // XXX: Pass real fd, then who frees bb?
+ buffer_init(bb, -1, NULL, b->fbuf, b->flen);
+ ms->offset = offset;
+ ms->eoffset = 0;
+ } else {
+ ms->offset = ms->eoffset + offset;
+ }
+ }
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ fprintf(stderr, "bb=[%p,%" SIZE_T_FORMAT "u,%"
+ SIZE_T_FORMAT "u], %d [b=%p,%"
+ SIZE_T_FORMAT "u,%" SIZE_T_FORMAT "u], [o=%#x, c=%d]\n",
+ bb->fbuf, bb->flen, bb->elen, ms->offset, b->fbuf,
+ b->flen, b->elen, offset, cont_level);
+ }
+ return 0;
+}
+
+file_private int
+save_cont(struct magic_set *ms, struct cont *c)
+{
+ size_t len;
+ *c = ms->c;
+ len = c->len * sizeof(*c->li);
+ ms->c.li = CAST(struct level_info *, malloc(len));
+ if (ms->c.li == NULL) {
+ ms->c = *c;
+ return -1;
+ }
+ memcpy(ms->c.li, c->li, len);
+ return 0;
+}
+
+file_private void
+restore_cont(struct magic_set *ms, struct cont *c)
+{
+ free(ms->c.li);
+ ms->c = *c;
+}
+
+file_private int
+mget(struct magic_set *ms, struct magic *m, const struct buffer *b,
+ const unsigned char *s, size_t nbytes, size_t o, unsigned int cont_level,
+ int mode, int text, int flip, uint16_t *indir_count, uint16_t *name_count,
+ int *printed_something, int *need_separator, int *firstline, int *returnval,
+ int *found_match)
+{
+ uint32_t eoffset, offset = ms->offset;
+ struct buffer bb;
+ intmax_t lhs;
+ file_pushbuf_t *pb;
+ int rv, oneed_separator, in_type, nfound_match;
+ char *rbuf;
+ union VALUETYPE *p = &ms->ms_value;
+ struct mlist ml, *mlp;
+ struct cont c;
+
+ if (*indir_count >= ms->indir_max) {
+ file_error(ms, 0, "indirect count (%hu) exceeded",
+ *indir_count);
+ return -1;
+ }
+
+ if (*name_count >= ms->name_max) {
+ file_error(ms, 0, "name use count (%hu) exceeded",
+ *name_count);
+ return -1;
+ }
+
+
+
+ if (mcopy(ms, p, m->type, m->flag & INDIR, s,
+ CAST(uint32_t, offset + o), CAST(uint32_t, nbytes), m) == -1)
+ return -1;
+
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ fprintf(stderr, "mget(type=%d, flag=%#x, offset=%u, o=%"
+ SIZE_T_FORMAT "u, " "nbytes=%" SIZE_T_FORMAT
+ "u, il=%hu, nc=%hu)\n",
+ m->type, m->flag, offset, o, nbytes,
+ *indir_count, *name_count);
+ mdebug(offset, RCAST(char *, RCAST(void *, p)),
+ sizeof(union VALUETYPE));
+#ifndef COMPILE_ONLY
+ file_mdump(m);
+#endif
+ }
+
+ if (m->flag & INDIR) {
+ intmax_t off = m->in_offset;
+ const int sgn = m->in_op & FILE_OPSIGNED;
+ if (m->in_op & FILE_OPINDIRECT) {
+ const union VALUETYPE *q = CAST(const union VALUETYPE *,
+ RCAST(const void *, s + offset + off));
+ int op;
+ switch (op = cvt_flip(m->in_type, flip)) {
+ case FILE_BYTE:
+ if (OFFSET_OOB(nbytes, offset + off, 1))
+ return 0;
+ off = SEXT(sgn,8,q->b);
+ break;
+ case FILE_SHORT:
+ if (OFFSET_OOB(nbytes, offset + off, 2))
+ return 0;
+ off = SEXT(sgn,16,q->h);
+ break;
+ case FILE_BESHORT:
+ if (OFFSET_OOB(nbytes, offset + off, 2))
+ return 0;
+ off = SEXT(sgn,16,BE16(q));
+ break;
+ case FILE_LESHORT:
+ if (OFFSET_OOB(nbytes, offset + off, 2))
+ return 0;
+ off = SEXT(sgn,16,LE16(q));
+ break;
+ case FILE_LONG:
+ if (OFFSET_OOB(nbytes, offset + off, 4))
+ return 0;
+ off = SEXT(sgn,32,q->l);
+ break;
+ case FILE_BELONG:
+ case FILE_BEID3:
+ if (OFFSET_OOB(nbytes, offset + off, 4))
+ return 0;
+ off = SEXT(sgn,32,BE32(q));
+ break;
+ case FILE_LEID3:
+ case FILE_LELONG:
+ if (OFFSET_OOB(nbytes, offset + off, 4))
+ return 0;
+ off = SEXT(sgn,32,LE32(q));
+ break;
+ case FILE_MELONG:
+ if (OFFSET_OOB(nbytes, offset + off, 4))
+ return 0;
+ off = SEXT(sgn,32,ME32(q));
+ break;
+ case FILE_BEQUAD:
+ if (OFFSET_OOB(nbytes, offset + off, 8))
+ return 0;
+ off = SEXT(sgn,64,BE64(q));
+ break;
+ case FILE_LEQUAD:
+ if (OFFSET_OOB(nbytes, offset + off, 8))
+ return 0;
+ off = SEXT(sgn,64,LE64(q));
+ break;
+ case FILE_OCTAL:
+ if (OFFSET_OOB(nbytes, offset, m->vallen))
+ return 0;
+ off = SEXT(sgn,64,strtoull(p->s, NULL, 8));
+ break;
+ default:
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "bad op=%d\n", op);
+ return 0;
+ }
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "indirect offs=%jd\n", off);
+ }
+ switch (in_type = cvt_flip(m->in_type, flip)) {
+ case FILE_BYTE:
+ if (OFFSET_OOB(nbytes, offset, 1))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,8,p->b), off))
+ return 0;
+ break;
+ case FILE_BESHORT:
+ if (OFFSET_OOB(nbytes, offset, 2))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,16,BE16(p)), off))
+ return 0;
+ break;
+ case FILE_LESHORT:
+ if (OFFSET_OOB(nbytes, offset, 2))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,16,LE16(p)), off))
+ return 0;
+ break;
+ case FILE_SHORT:
+ if (OFFSET_OOB(nbytes, offset, 2))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,16,p->h), off))
+ return 0;
+ break;
+ case FILE_BELONG:
+ case FILE_BEID3:
+ if (OFFSET_OOB(nbytes, offset, 4))
+ return 0;
+ lhs = BE32(p);
+ if (in_type == FILE_BEID3)
+ lhs = cvt_id3(ms, CAST(uint32_t, lhs));
+ if (do_ops(m, &offset, SEXT(sgn,32,lhs), off))
+ return 0;
+ break;
+ case FILE_LELONG:
+ case FILE_LEID3:
+ if (OFFSET_OOB(nbytes, offset, 4))
+ return 0;
+ lhs = LE32(p);
+ if (in_type == FILE_LEID3)
+ lhs = cvt_id3(ms, CAST(uint32_t, lhs));
+ if (do_ops(m, &offset, SEXT(sgn,32,lhs), off))
+ return 0;
+ break;
+ case FILE_MELONG:
+ if (OFFSET_OOB(nbytes, offset, 4))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,32,ME32(p)), off))
+ return 0;
+ break;
+ case FILE_LONG:
+ if (OFFSET_OOB(nbytes, offset, 4))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,32,p->l), off))
+ return 0;
+ break;
+ case FILE_LEQUAD:
+ if (OFFSET_OOB(nbytes, offset, 8))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,64,LE64(p)), off))
+ return 0;
+ break;
+ case FILE_BEQUAD:
+ if (OFFSET_OOB(nbytes, offset, 8))
+ return 0;
+ if (do_ops(m, &offset, SEXT(sgn,64,BE64(p)), off))
+ return 0;
+ break;
+ case FILE_OCTAL:
+ if (OFFSET_OOB(nbytes, offset, m->vallen))
+ return 0;
+ if(do_ops(m, &offset,
+ SEXT(sgn,64,strtoull(p->s, NULL, 8)), off))
+ return 0;
+ break;
+ default:
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "bad in_type=%d\n", in_type);
+ return 0;
+ }
+
+ if (m->flag & INDIROFFADD) {
+ if (cont_level == 0) {
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr,
+ "indirect *zero* cont_level\n");
+ return 0;
+ }
+ offset += ms->c.li[cont_level - 1].off;
+ if (offset == 0) {
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr,
+ "indirect *zero* offset\n");
+ return 0;
+ }
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "indirect +offs=%u\n", offset);
+ }
+ if (mcopy(ms, p, m->type, 0, s, offset, nbytes, m) == -1)
+ return -1;
+ ms->offset = offset;
+
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ mdebug(offset, RCAST(char *, RCAST(void *, p)),
+ sizeof(union VALUETYPE));
+#ifndef COMPILE_ONLY
+ file_mdump(m);
+#endif
+ }
+ }
+
+ /* Verify we have enough data to match magic type */
+ switch (m->type) {
+ case FILE_BYTE:
+ if (OFFSET_OOB(nbytes, offset, 1))
+ return 0;
+ break;
+
+ case FILE_SHORT:
+ case FILE_BESHORT:
+ case FILE_LESHORT:
+ if (OFFSET_OOB(nbytes, offset, 2))
+ return 0;
+ break;
+
+ case FILE_LONG:
+ case FILE_BELONG:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ case FILE_DATE:
+ case FILE_BEDATE:
+ case FILE_LEDATE:
+ case FILE_MEDATE:
+ case FILE_LDATE:
+ case FILE_BELDATE:
+ case FILE_LELDATE:
+ case FILE_MELDATE:
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ if (OFFSET_OOB(nbytes, offset, 4))
+ return 0;
+ break;
+
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ if (OFFSET_OOB(nbytes, offset, 8))
+ return 0;
+ break;
+
+ case FILE_GUID:
+ if (OFFSET_OOB(nbytes, offset, 16))
+ return 0;
+ break;
+
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_SEARCH:
+ case FILE_OCTAL:
+ if (OFFSET_OOB(nbytes, offset, m->vallen))
+ return 0;
+ break;
+
+ case FILE_REGEX:
+ if (nbytes < offset)
+ return 0;
+ break;
+
+ case FILE_INDIRECT:
+ if (m->str_flags & INDIRECT_RELATIVE)
+ offset += CAST(uint32_t, o);
+ if (offset == 0)
+ return 0;
+
+ if (nbytes < offset)
+ return 0;
+
+ if ((pb = file_push_buffer(ms)) == NULL)
+ return -1;
+
+ (*indir_count)++;
+ bb = *b;
+ bb.fbuf = s + offset;
+ bb.flen = nbytes - offset;
+ rv = -1;
+ for (mlp = ms->mlist[0]->next; mlp != ms->mlist[0];
+ mlp = mlp->next)
+ {
+ if ((rv = match(ms, mlp->magic, mlp->magic_rxcomp,
+ mlp->nmagic, &bb, 0, BINTEST, text, 0, indir_count,
+ name_count, printed_something, need_separator,
+ firstline, NULL, NULL)) != 0)
+ break;
+ }
+
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ fprintf(stderr, "indirect @offs=%u[%d]\n", offset, rv);
+
+ rbuf = file_pop_buffer(ms, pb);
+ if (rbuf == NULL && ms->event_flags & EVENT_HAD_ERR)
+ return -1;
+
+ if (rv == 1) {
+ if ((ms->flags & MAGIC_NODESC) == 0 &&
+ file_printf(ms, F(ms, m->desc, "%u"), offset) == -1)
+ {
+ free(rbuf);
+ return -1;
+ }
+ if (file_printf(ms, "%s", rbuf) == -1) {
+ free(rbuf);
+ return -1;
+ }
+ }
+ free(rbuf);
+ return rv;
+
+ case FILE_USE:
+ if (nbytes < offset)
+ return 0;
+ rbuf = m->value.s;
+ if (*rbuf == '^') {
+ rbuf++;
+ flip = !flip;
+ }
+ if (file_magicfind(ms, rbuf, &ml) == -1) {
+ file_error(ms, 0, "cannot find entry `%s'", rbuf);
+ return -1;
+ }
+ if (save_cont(ms, &c) == -1) {
+ file_error(ms, errno, "can't allocate continuation");
+ return -1;
+ }
+
+ oneed_separator = *need_separator;
+ if (m->flag & NOSPACE)
+ *need_separator = 0;
+
+ nfound_match = 0;
+ (*name_count)++;
+ eoffset = ms->eoffset;
+ rv = match(ms, ml.magic, ml.magic_rxcomp, ml.nmagic, b,
+ offset + o, mode, text, flip, indir_count, name_count,
+ printed_something, need_separator, firstline, returnval,
+ &nfound_match);
+ ms->ms_value.q = nfound_match;
+ (*name_count)--;
+ *found_match |= nfound_match;
+
+ restore_cont(ms, &c);
+
+ if (rv != 1)
+ *need_separator = oneed_separator;
+ ms->offset = offset;
+ ms->eoffset = eoffset;
+ return rv || *found_match;
+
+ case FILE_NAME:
+ if (ms->flags & MAGIC_NODESC)
+ return 1;
+ if (file_printf(ms, "%s", m->desc) == -1)
+ return -1;
+ return 1;
+ case FILE_DER:
+ case FILE_DEFAULT: /* nothing to check */
+ case FILE_CLEAR:
+ default:
+ break;
+ }
+ if (!mconvert(ms, m, flip))
+ return 0;
+ return 1;
+}
+
+file_private uint64_t
+file_strncmp(const char *s1, const char *s2, size_t len, size_t maxlen,
+ uint32_t flags)
+{
+ /*
+ * Convert the source args to unsigned here so that (1) the
+ * compare will be unsigned as it is in strncmp() and (2) so
+ * the ctype functions will work correctly without extra
+ * casting.
+ */
+ const unsigned char *a = RCAST(const unsigned char *, s1);
+ const unsigned char *b = RCAST(const unsigned char *, s2);
+ uint32_t ws = flags & (STRING_COMPACT_WHITESPACE |
+ STRING_COMPACT_OPTIONAL_WHITESPACE);
+ const unsigned char *eb = b + (ws ? maxlen : len);
+ uint64_t v;
+
+ /*
+ * What we want here is v = strncmp(s1, s2, len),
+ * but ignoring any nulls.
+ */
+ v = 0;
+ len++;
+ if (0L == flags) { /* normal string: do it fast */
+ while (--len > 0)
+ if ((v = *b++ - *a++) != '\0')
+ break;
+ }
+ else { /* combine the others */
+ while (--len > 0) {
+ if (b >= eb) {
+ v = 1;
+ break;
+ }
+ if ((flags & STRING_IGNORE_LOWERCASE) &&
+ islower(*a)) {
+ if ((v = tolower(*b++) - *a++) != '\0')
+ break;
+ }
+ else if ((flags & STRING_IGNORE_UPPERCASE) &&
+ isupper(*a)) {
+ if ((v = toupper(*b++) - *a++) != '\0')
+ break;
+ }
+ else if ((flags & STRING_COMPACT_WHITESPACE) &&
+ isspace(*a)) {
+ a++;
+ if (isspace(*b)) {
+ b++;
+ if (!isspace(*a))
+ while (b < eb && isspace(*b))
+ b++;
+ }
+ else {
+ v = 1;
+ break;
+ }
+ }
+ else if ((flags & STRING_COMPACT_OPTIONAL_WHITESPACE) &&
+ isspace(*a)) {
+ a++;
+ while (b < eb && isspace(*b))
+ b++;
+ }
+ else {
+ if ((v = *b++ - *a++) != '\0')
+ break;
+ }
+ }
+ if (len == 0 && v == 0 && (flags & STRING_FULL_WORD)) {
+ if (*b && !isspace(*b))
+ v = 1;
+ }
+ }
+ return v;
+}
+
+file_private uint64_t
+file_strncmp16(const char *a, const char *b, size_t len, size_t maxlen,
+ uint32_t flags)
+{
+ /*
+ * XXX - The 16-bit string compare probably needs to be done
+ * differently, especially if the flags are to be supported.
+ * At the moment, I am unsure.
+ */
+ flags = 0;
+ return file_strncmp(a, b, len, maxlen, flags);
+}
+
+file_private file_regex_t *
+alloc_regex(struct magic_set *ms, struct magic *m)
+{
+ int rc;
+ file_regex_t *rx = CAST(file_regex_t *, malloc(sizeof(*rx)));
+
+ if (rx == NULL) {
+ file_error(ms, errno, "can't allocate %" SIZE_T_FORMAT
+ "u bytes", sizeof(*rx));
+ return NULL;
+ }
+
+ rc = file_regcomp(ms, rx, m->value.s, REG_EXTENDED | REG_NEWLINE |
+ ((m->str_flags & STRING_IGNORE_CASE) ? REG_ICASE : 0));
+ if (rc == 0)
+ return rx;
+
+ free(rx);
+ return NULL;
+}
+
+file_private int
+magiccheck(struct magic_set *ms, struct magic *m, file_regex_t **m_cache)
+{
+ uint64_t l = m->value.q;
+ uint64_t v;
+ float fl, fv;
+ double dl, dv;
+ int matched;
+ union VALUETYPE *p = &ms->ms_value;
+
+ switch (m->type) {
+ case FILE_BYTE:
+ v = p->b;
+ break;
+
+ case FILE_SHORT:
+ case FILE_BESHORT:
+ case FILE_LESHORT:
+ case FILE_MSDOSDATE:
+ case FILE_LEMSDOSDATE:
+ case FILE_BEMSDOSDATE:
+ case FILE_MSDOSTIME:
+ case FILE_LEMSDOSTIME:
+ case FILE_BEMSDOSTIME:
+ v = p->h;
+ break;
+
+ case FILE_LONG:
+ case FILE_BELONG:
+ case FILE_LELONG:
+ case FILE_MELONG:
+ case FILE_DATE:
+ case FILE_BEDATE:
+ case FILE_LEDATE:
+ case FILE_MEDATE:
+ case FILE_LDATE:
+ case FILE_BELDATE:
+ case FILE_LELDATE:
+ case FILE_MELDATE:
+ v = p->l;
+ break;
+
+ case FILE_QUAD:
+ case FILE_LEQUAD:
+ case FILE_BEQUAD:
+ case FILE_QDATE:
+ case FILE_BEQDATE:
+ case FILE_LEQDATE:
+ case FILE_QLDATE:
+ case FILE_BEQLDATE:
+ case FILE_LEQLDATE:
+ case FILE_QWDATE:
+ case FILE_BEQWDATE:
+ case FILE_LEQWDATE:
+ case FILE_OFFSET:
+ v = p->q;
+ break;
+
+ case FILE_FLOAT:
+ case FILE_BEFLOAT:
+ case FILE_LEFLOAT:
+ fl = m->value.f;
+ fv = p->f;
+ switch (m->reln) {
+ case 'x':
+ matched = 1;
+ break;
+
+ case '!':
+ matched = isunordered(fl, fv) ? 1 : fv != fl;
+ break;
+
+ case '=':
+ matched = isunordered(fl, fv) ? 0 : fv == fl;
+ break;
+
+ case '>':
+ matched = isgreater(fv, fl);
+ break;
+
+ case '<':
+ matched = isless(fv, fl);
+ break;
+
+ default:
+ file_magerror(ms, "cannot happen with float: "
+ "invalid relation `%c'", m->reln);
+ return -1;
+ }
+ return matched;
+
+ case FILE_DOUBLE:
+ case FILE_BEDOUBLE:
+ case FILE_LEDOUBLE:
+ dl = m->value.d;
+ dv = p->d;
+ switch (m->reln) {
+ case 'x':
+ matched = 1;
+ break;
+
+ case '!':
+ matched = isunordered(dv, dl) ? 1 : dv != dl;
+ break;
+
+ case '=':
+ matched = isunordered(dv, dl) ? 0 : dv == dl;
+ break;
+
+ case '>':
+ matched = isgreater(dv, dl);
+ break;
+
+ case '<':
+ matched = isless(dv, dl);
+ break;
+
+ default:
+ file_magerror(ms, "cannot happen with double: "
+ "invalid relation `%c'", m->reln);
+ return -1;
+ }
+ return matched;
+
+ case FILE_DEFAULT:
+ case FILE_CLEAR:
+ l = 0;
+ v = 0;
+ break;
+
+ case FILE_STRING:
+ case FILE_PSTRING:
+ case FILE_OCTAL:
+ l = 0;
+ v = file_strncmp(m->value.s, p->s, CAST(size_t, m->vallen),
+ sizeof(p->s), m->str_flags);
+ break;
+
+ case FILE_BESTRING16:
+ case FILE_LESTRING16:
+ l = 0;
+ v = file_strncmp16(m->value.s, p->s, CAST(size_t, m->vallen),
+ sizeof(p->s), m->str_flags);
+ break;
+
+ case FILE_SEARCH: { /* search ms->search.s for the string m->value.s */
+ size_t slen;
+ size_t idx;
+
+ if (ms->search.s == NULL)
+ return 0;
+
+ slen = MIN(m->vallen, sizeof(m->value.s));
+ l = 0;
+ v = 0;
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ fprintf(stderr, "search: [");
+ file_showstr(stderr, ms->search.s, ms->search.s_len);
+ fprintf(stderr, "] for [");
+ file_showstr(stderr, m->value.s, slen);
+ }
+#ifdef HAVE_MEMMEM
+ if (slen > 0 && m->str_flags == 0) {
+ const char *found;
+ idx = m->str_range + slen;
+ if (m->str_range == 0 || ms->search.s_len < idx)
+ idx = ms->search.s_len;
+ found = CAST(const char *, memmem(ms->search.s, idx,
+ m->value.s, slen));
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ fprintf(stderr, "] %sfound\n",
+ found ? "" : "not ");
+ }
+ if (!found) {
+ v = 1;
+ break;
+ }
+ idx = found - ms->search.s;
+ ms->search.offset += idx;
+ ms->search.rm_len = ms->search.s_len - idx;
+ break;
+ }
+#endif
+
+ for (idx = 0; m->str_range == 0 || idx < m->str_range; idx++) {
+ if (slen + idx > ms->search.s_len) {
+ v = 1;
+ break;
+ }
+
+ v = file_strncmp(m->value.s, ms->search.s + idx, slen,
+ ms->search.s_len - idx, m->str_flags);
+ if (v == 0) { /* found match */
+ ms->search.offset += idx;
+ ms->search.rm_len = ms->search.s_len - idx;
+ break;
+ }
+ }
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ fprintf(stderr, "] %sfound\n", v == 0 ? "" : "not ");
+ }
+ break;
+ }
+ case FILE_REGEX: {
+ int rc;
+ file_regex_t *rx = *m_cache;
+ const char *search;
+ regmatch_t pmatch;
+ size_t slen = ms->search.s_len;
+ char *copy;
+
+ if (ms->search.s == NULL)
+ return 0;
+
+ if (rx == NULL) {
+ rx = *m_cache = alloc_regex(ms, m);
+ if (rx == NULL)
+ return -1;
+ }
+ l = 0;
+ if (slen != 0) {
+ copy = CAST(char *, malloc(slen));
+ if (copy == NULL) {
+ file_error(ms, errno,
+ "can't allocate %" SIZE_T_FORMAT "u bytes",
+ slen);
+ return -1;
+ }
+ memcpy(copy, ms->search.s, slen);
+ copy[--slen] = '\0';
+ search = copy;
+ } else {
+ search = CCAST(char *, "");
+ copy = NULL;
+ }
+ rc = file_regexec(ms, rx, RCAST(const char *, search),
+ 1, &pmatch, 0);
+ free(copy);
+ switch (rc) {
+ case 0:
+ ms->search.s += CAST(int, pmatch.rm_so);
+ ms->search.offset += CAST(size_t, pmatch.rm_so);
+ ms->search.rm_len = CAST(size_t,
+ pmatch.rm_eo - pmatch.rm_so);
+ v = 0;
+ break;
+
+ case REG_NOMATCH:
+ v = 1;
+ break;
+
+ default:
+ return -1;
+ }
+ break;
+ }
+ case FILE_USE:
+ return ms->ms_value.q != 0;
+ case FILE_NAME:
+ case FILE_INDIRECT:
+ return 1;
+ case FILE_DER:
+ matched = der_cmp(ms, m);
+ if (matched == -1) {
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
+ (void) fprintf(stderr,
+ "EOF comparing DER entries\n");
+ }
+ return 0;
+ }
+ return matched;
+ case FILE_GUID:
+ l = 0;
+ v = memcmp(m->value.guid, p->guid, sizeof(p->guid));
+ break;
+ default:
+ file_magerror(ms, "invalid type %d in magiccheck()", m->type);
+ return -1;
+ }
+
+ v = file_signextend(ms, m, v);
+
+ switch (m->reln) {
+ case 'x':
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT
+ "u == *any* = 1\n", CAST(unsigned long long, v));
+ matched = 1;
+ break;
+
+ case '!':
+ matched = v != l;
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT "u != %"
+ INT64_T_FORMAT "u = %d\n",
+ CAST(unsigned long long, v),
+ CAST(unsigned long long, l), matched);
+ break;
+
+ case '=':
+ matched = v == l;
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT "u == %"
+ INT64_T_FORMAT "u = %d\n",
+ CAST(unsigned long long, v),
+ CAST(unsigned long long, l), matched);
+ break;
+
+ case '>':
+ if (m->flag & UNSIGNED) {
+ matched = v > l;
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT
+ "u > %" INT64_T_FORMAT "u = %d\n",
+ CAST(unsigned long long, v),
+ CAST(unsigned long long, l), matched);
+ }
+ else {
+ matched = CAST(int64_t, v) > CAST(int64_t, l);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT
+ "d > %" INT64_T_FORMAT "d = %d\n",
+ CAST(long long, v),
+ CAST(long long, l), matched);
+ }
+ break;
+
+ case '<':
+ if (m->flag & UNSIGNED) {
+ matched = v < l;
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT
+ "u < %" INT64_T_FORMAT "u = %d\n",
+ CAST(unsigned long long, v),
+ CAST(unsigned long long, l), matched);
+ }
+ else {
+ matched = CAST(int64_t, v) < CAST(int64_t, l);
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "%" INT64_T_FORMAT
+ "d < %" INT64_T_FORMAT "d = %d\n",
+ CAST(long long, v),
+ CAST(long long, l), matched);
+ }
+ break;
+
+ case '&':
+ matched = (v & l) == l;
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "((%" INT64_T_FORMAT "x & %"
+ INT64_T_FORMAT "x) == %" INT64_T_FORMAT
+ "x) = %d\n", CAST(unsigned long long, v),
+ CAST(unsigned long long, l),
+ CAST(unsigned long long, l),
+ matched);
+ break;
+
+ case '^':
+ matched = (v & l) != l;
+ if ((ms->flags & MAGIC_DEBUG) != 0)
+ (void) fprintf(stderr, "((%" INT64_T_FORMAT "x & %"
+ INT64_T_FORMAT "x) != %" INT64_T_FORMAT
+ "x) = %d\n", CAST(unsigned long long, v),
+ CAST(unsigned long long, l),
+ CAST(unsigned long long, l), matched);
+ break;
+
+ default:
+ file_magerror(ms, "cannot happen: invalid relation `%c'",
+ m->reln);
+ return -1;
+ }
+
+ return matched;
+}
+
+file_private int
+handle_annotation(struct magic_set *ms, struct magic *m, int firstline)
+{
+ if ((ms->flags & MAGIC_APPLE) && m->apple[0]) {
+ if (print_sep(ms, firstline) == -1)
+ return -1;
+ if (file_printf(ms, "%.8s", m->apple) == -1)
+ return -1;
+ return 1;
+ }
+ if ((ms->flags & MAGIC_EXTENSION) && m->ext[0]) {
+ if (print_sep(ms, firstline) == -1)
+ return -1;
+ if (file_printf(ms, "%s", m->ext) == -1)
+ return -1;
+ return 1;
+ }
+ if ((ms->flags & MAGIC_MIME_TYPE) && m->mimetype[0]) {
+ char buf[1024];
+ const char *p;
+ if (print_sep(ms, firstline) == -1)
+ return -1;
+ if (varexpand(ms, buf, sizeof(buf), m->mimetype) == -1)
+ p = m->mimetype;
+ else
+ p = buf;
+ if (file_printf(ms, "%s", p) == -1)
+ return -1;
+ return 1;
+ }
+ return 0;
+}
+
+file_private int
+print_sep(struct magic_set *ms, int firstline)
+{
+ if (firstline)
+ return 0;
+ /*
+ * we found another match
+ * put a newline and '-' to do some simple formatting
+ */
+ return file_separator(ms);
+}
diff --git a/src/strcasestr.c b/src/strcasestr.c
new file mode 100644
index 0000000..3db407f
--- /dev/null
+++ b/src/strcasestr.c
@@ -0,0 +1,84 @@
+/* $NetBSD: strcasestr.c,v 1.3 2005/11/29 03:12:00 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * 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 the University nor the names of its 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.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: strcasestr.c,v 1.3 2005/11/29 03:12:00 christos Exp $");
+__RCSID("$NetBSD: strncasecmp.c,v 1.2 2007/06/04 18:19:27 christos Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include "file.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+static int
+_strncasecmp(const char *s1, const char *s2, size_t n)
+{
+ if (n != 0) {
+ const unsigned char *us1 = (const unsigned char *)s1,
+ *us2 = (const unsigned char *)s2;
+
+ do {
+ if (tolower(*us1) != tolower(*us2++))
+ return tolower(*us1) - tolower(*--us2);
+ if (*us1++ == '\0')
+ break;
+ } while (--n != 0);
+ }
+ return 0;
+}
+
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
+char *
+strcasestr(const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != 0) {
+ c = tolower((unsigned char)c);
+ len = strlen(find);
+ do {
+ do {
+ if ((sc = *s++) == 0)
+ return (NULL);
+ } while ((char)tolower((unsigned char)sc) != c);
+ } while (_strncasecmp(s, find, len) != 0);
+ s--;
+ }
+ return (char *)(intptr_t)(s);
+}
diff --git a/src/strlcat.c b/src/strlcat.c
new file mode 100644
index 0000000..978340c
--- /dev/null
+++ b/src/strlcat.c
@@ -0,0 +1,61 @@
+/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strlcat.c */
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: strlcat.c,v 1.5 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/src/strlcpy.c b/src/strlcpy.c
new file mode 100644
index 0000000..ed1f4cc
--- /dev/null
+++ b/src/strlcpy.c
@@ -0,0 +1,57 @@
+/* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strlcpy.c */
+#include "file.h"
+#ifndef lint
+FILE_RCSID("@(#)$File: strlcpy.c,v 1.5 2022/09/24 20:30:13 christos Exp $")
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/src/tar.h b/src/tar.h
new file mode 100644
index 0000000..ced4f39
--- /dev/null
+++ b/src/tar.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*
+ * Header file for file_public domain tar (tape archive) program.
+ *
+ * @(#)tar.h 1.20 86/10/29 Public Domain.
+ *
+ * Created 25 August 1985 by John Gilmore, ihnp4!hoptoad!gnu.
+ *
+ * $File: tar.h,v 1.16 2022/12/26 17:31:14 christos Exp $ # checkin only
+ */
+
+/*
+ * Header block on tape.
+ *
+ * I'm going to use traditional DP naming conventions here.
+ * A "block" is a big chunk of stuff that we do I/O on.
+ * A "record" is a piece of info that we care about.
+ * Typically many "record"s fit into a "block".
+ */
+#define RECORDSIZE 512
+#define NAMSIZ 100
+#define TUNMLEN 32
+#define TGNMLEN 32
+
+union record {
+ unsigned char charptr[RECORDSIZE];
+ struct header {
+ char name[NAMSIZ];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char chksum[8];
+ char linkflag;
+ char linkname[NAMSIZ];
+ char magic[8];
+ char uname[TUNMLEN];
+ char gname[TGNMLEN];
+ char devmajor[8];
+ char devminor[8];
+ } header;
+};
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar" /* 5 chars and a null */
+#define GNUTMAGIC "ustar " /* 7 chars and a null */
diff --git a/src/vasprintf.c b/src/vasprintf.c
new file mode 100644
index 0000000..940e8a4
--- /dev/null
+++ b/src/vasprintf.c
@@ -0,0 +1,650 @@
+/*
+ * Copyright (c) Ian F. Darwin 1986-1995.
+ * Software written by Ian F. Darwin and others;
+ * maintained 1995-present by Christos Zoulas and others.
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+/*###########################################################################
+ # #
+ # vasprintf #
+ # #
+ # Copyright (c) 2002-2005 David TAILLANDIER #
+ # #
+ ###########################################################################*/
+
+/*
+
+This software is distributed under the "modified BSD licence".
+
+This software is also released with GNU license (GPL) in another file (same
+source-code, only license differ).
+
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer. 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. The name of the author may not be used to
+endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+
+====================
+
+Hacked from xnprintf version of 26th February 2005 to provide only
+vasprintf by Reuben Thomas <rrt@sc3d.org>.
+
+====================
+
+
+'printf' function family use the following format string:
+
+%[flag][width][.prec][modifier]type
+
+%% is the escape sequence to print a '%'
+% followed by an unknown format will print the characters without
+trying to do any interpretation
+
+flag: none + - # (blank)
+width: n 0n *
+prec: none .0 .n .*
+modifier: F N L h l ll z t ('F' and 'N' are ms-dos/16-bit specific)
+type: d i o u x X f e g E G c s p n
+
+
+The function needs to allocate memory to store the full text before to
+actually writing it. i.e if you want to fnprintf() 1000 characters, the
+functions will allocate 1000 bytes.
+This behaviour can be modified: you have to customise the code to flush the
+internal buffer (writing to screen or file) when it reach a given size. Then
+the buffer can have a shorter length. But what? If you really need to write
+HUGE string, don't use printf!
+During the process, some other memory is allocated (1024 bytes minimum)
+to handle the output of partial sprintf() calls. If you have only 10000 bytes
+free in memory, you *may* not be able to nprintf() an 8000 bytes-long text.
+
+note: if a buffer overflow occurs, exit() is called. This situation should
+never appear ... but if you want to be *really* sure, you have to modify the
+code to handle those situations (only one place to modify).
+A buffer overflow can only occur if your sprintf() do strange things or when
+you use strange formats.
+
+*/
+#include "file.h"
+
+#ifndef lint
+FILE_RCSID("@(#)$File: vasprintf.c,v 1.23 2022/09/24 20:30:13 christos Exp $")
+#endif /* lint */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+
+#define ALLOC_CHUNK 2048
+#define ALLOC_SECURITY_MARGIN 1024 /* big value because some platforms have very big 'G' exponent */
+#if ALLOC_CHUNK < ALLOC_SECURITY_MARGIN
+# error !!! ALLOC_CHUNK < ALLOC_SECURITY_MARGIN !!!
+#endif
+/* note: to have some interest, ALLOC_CHUNK should be much greater than ALLOC_SECURITY_MARGIN */
+
+/*
+ * To save a lot of push/pop, every variable are stored into this
+ * structure, which is passed among nearly every sub-functions.
+ */
+typedef struct {
+ const char * src_string; /* current position into input string */
+ char * buffer_base; /* output buffer */
+ char * dest_string; /* current position into output string */
+ size_t buffer_len; /* length of output buffer */
+ size_t real_len; /* real current length of output text */
+ size_t pseudo_len; /* total length of output text if it were not limited in size */
+ size_t maxlen;
+ va_list vargs; /* pointer to current position into vargs */
+} xprintf_struct;
+
+/*
+ * Realloc buffer if needed
+ * Return value: 0 = ok
+ * EOF = not enough memory
+ */
+static int realloc_buff(xprintf_struct *s, size_t len)
+{
+ char * ptr;
+
+ if (len + ALLOC_SECURITY_MARGIN + s->real_len > s->buffer_len) {
+ len += s->real_len + ALLOC_CHUNK;
+ ptr = (char *)realloc((void *)(s->buffer_base), len);
+ if (ptr == NULL) {
+ s->buffer_base = NULL;
+ return EOF;
+ }
+
+ s->dest_string = ptr + (size_t)(s->dest_string - s->buffer_base);
+ s->buffer_base = ptr;
+ s->buffer_len = len;
+
+ (s->buffer_base)[s->buffer_len - 1] = 1; /* overflow marker */
+ }
+
+ return 0;
+}
+
+/*
+ * Prints 'usual' characters up to next '%'
+ * or up to end of text
+ */
+static int usual_char(xprintf_struct * s)
+{
+ size_t len;
+
+ len = strcspn(s->src_string, "%"); /* reaches the next '%' or end of input string */
+ /* note: 'len' is never 0 because the presence of '%' */
+ /* or end-of-line is checked in the calling function */
+
+ if (realloc_buff(s,len) == EOF)
+ return EOF;
+
+ memcpy(s->dest_string, s->src_string, len);
+ s->src_string += len;
+ s->dest_string += len;
+ s->real_len += len;
+ s->pseudo_len += len;
+
+ return 0;
+}
+
+/*
+ * Return value: 0 = ok
+ * EOF = error
+ */
+static int print_it(xprintf_struct *s, size_t approx_len,
+ const char *format_string, ...)
+{
+ va_list varg;
+ int vsprintf_len;
+ size_t len;
+
+ if (realloc_buff(s,approx_len) == EOF)
+ return EOF;
+
+ va_start(varg, format_string);
+ vsprintf_len = vsprintf(s->dest_string, format_string, varg);
+ va_end(varg);
+
+ /* Check for overflow */
+ assert((s->buffer_base)[s->buffer_len - 1] == 1);
+
+ if (vsprintf_len == EOF) /* must be done *after* overflow-check */
+ return EOF;
+
+ s->pseudo_len += vsprintf_len;
+ len = strlen(s->dest_string);
+ s->real_len += len;
+ s->dest_string += len;
+
+ return 0;
+}
+
+/*
+ * Prints a string (%s)
+ * We need special handling because:
+ * a: the length of the string is unknown
+ * b: when .prec is used, we must not access any extra byte of the
+ * string (of course, if the original sprintf() does... what the
+ * hell, not my problem)
+ *
+ * Return value: 0 = ok
+ * EOF = error
+ */
+static int type_s(xprintf_struct *s, int width, int prec,
+ const char *format_string, const char *arg_string)
+{
+ size_t string_len;
+
+ if (arg_string == NULL)
+ return print_it(s, (size_t)6, "(null)", 0);
+
+ /* hand-made strlen() which stops when 'prec' is reached. */
+ /* if 'prec' is -1 then it is never reached. */
+ string_len = 0;
+ while (arg_string[string_len] != 0 && (size_t)prec != string_len)
+ string_len++;
+
+ if (width != -1 && string_len < (size_t)width)
+ string_len = (size_t)width;
+
+ return print_it(s, string_len, format_string, arg_string);
+}
+
+/*
+ * Read a series of digits. Stop when non-digit is found.
+ * Return value: the value read (between 0 and 32767).
+ * Note: no checks are made against overflow. If the string contain a big
+ * number, then the return value won't be what we want (but, in this case,
+ * the programmer don't know whatr he wants, then no problem).
+ */
+static int getint(const char **string)
+{
+ int i = 0;
+
+ while (isdigit((unsigned char)**string) != 0) {
+ i = i * 10 + (**string - '0');
+ (*string)++;
+ }
+
+ if (i < 0 || i > 32767)
+ i = 32767; /* if we have i==-10 this is not because the number is */
+ /* negative; this is because the number is big */
+ return i;
+}
+
+/*
+ * Read a part of the format string. A part is 'usual characters' (ie "blabla")
+ * or '%%' escape sequence (to print a single '%') or any combination of
+ * format specifier (ie "%i" or "%10.2d").
+ * After the current part is managed, the function returns to caller with
+ * everything ready to manage the following part.
+ * The caller must ensure than the string is not empty, i.e. the first byte
+ * is not zero.
+ *
+ * Return value: 0 = ok
+ * EOF = error
+ */
+static int dispatch(xprintf_struct *s)
+{
+ const char *initial_ptr;
+ char format_string[24]; /* max length may be something like "% +-#032768.32768Ld" */
+ char *format_ptr;
+ int flag_plus, flag_minus, flag_space, flag_sharp, flag_zero;
+ int width, prec, modifier, approx_width;
+ char type;
+ /* most of those variables are here to rewrite the format string */
+
+#define SRCTXT (s->src_string)
+#define DESTTXT (s->dest_string)
+
+ /* incoherent format string. Characters after the '%' will be printed with the next call */
+#define INCOHERENT() do {SRCTXT=initial_ptr; return 0;} while (0) /* do/while to avoid */
+#define INCOHERENT_TEST() do {if(*SRCTXT==0) INCOHERENT();} while (0) /* a null statement */
+
+ /* 'normal' text */
+ if (*SRCTXT != '%')
+ return usual_char(s);
+
+ /* we then have a '%' */
+ SRCTXT++;
+ /* don't check for end-of-string ; this is done later */
+
+ /* '%%' escape sequence */
+ if (*SRCTXT == '%') {
+ if (realloc_buff(s, (size_t)1) == EOF) /* because we can have "%%%%%%%%..." */
+ return EOF;
+ *DESTTXT = '%';
+ DESTTXT++;
+ SRCTXT++;
+ (s->real_len)++;
+ (s->pseudo_len)++;
+ return 0;
+ }
+
+ /* '%' managing */
+ initial_ptr = SRCTXT; /* save current pointer in case of incorrect */
+ /* 'decoding'. Points just after the '%' so the '%' */
+ /* won't be printed in any case, as required. */
+
+ /* flag */
+ flag_plus = flag_minus = flag_space = flag_sharp = flag_zero = 0;
+
+ for (;; SRCTXT++) {
+ if (*SRCTXT == ' ')
+ flag_space = 1;
+ else if (*SRCTXT == '+')
+ flag_plus = 1;
+ else if (*SRCTXT == '-')
+ flag_minus = 1;
+ else if (*SRCTXT == '#')
+ flag_sharp = 1;
+ else if (*SRCTXT == '0')
+ flag_zero = 1;
+ else
+ break;
+ }
+
+ INCOHERENT_TEST(); /* here is the first test for end of string */
+
+ /* width */
+ if (*SRCTXT == '*') { /* width given by next argument */
+ SRCTXT++;
+ width = va_arg(s->vargs, int);
+ if ((size_t)width > 0x3fffU) /* 'size_t' to check against negative values too */
+ width = 0x3fff;
+ } else if (isdigit((unsigned char)*SRCTXT)) /* width given as ASCII number */
+ width = getint(&SRCTXT);
+ else
+ width = -1; /* no width specified */
+
+ INCOHERENT_TEST();
+
+ /* .prec */
+ if (*SRCTXT == '.') {
+ SRCTXT++;
+ if (*SRCTXT == '*') { /* .prec given by next argument */
+ SRCTXT++;
+ prec = va_arg(s->vargs, int);
+ if ((size_t)prec >= 0x3fffU) /* 'size_t' to check against negative values too */
+ prec = 0x3fff;
+ } else { /* .prec given as ASCII number */
+ if (isdigit((unsigned char)*SRCTXT) == 0)
+ INCOHERENT();
+ prec = getint(&SRCTXT);
+ }
+ INCOHERENT_TEST();
+ } else
+ prec = -1; /* no .prec specified */
+
+ /* modifier */
+ switch (*SRCTXT) {
+ case 'L':
+ case 'h':
+ case 'l':
+ case 'z':
+ case 't':
+ modifier = *SRCTXT;
+ SRCTXT++;
+ if (modifier=='l' && *SRCTXT=='l') {
+ SRCTXT++;
+ modifier = 'L'; /* 'll' == 'L' long long == long double */
+ } /* only for compatibility ; not portable */
+ INCOHERENT_TEST();
+ break;
+ default:
+ modifier = -1; /* no modifier specified */
+ break;
+ }
+
+ /* type */
+ type = *SRCTXT;
+ if (strchr("diouxXfegEGcspn",type) == NULL)
+ INCOHERENT(); /* unknown type */
+ SRCTXT++;
+
+ /* rewrite format-string */
+ format_string[0] = '%';
+ format_ptr = &(format_string[1]);
+
+ if (flag_plus) {
+ *format_ptr = '+';
+ format_ptr++;
+ }
+ if (flag_minus) {
+ *format_ptr = '-';
+ format_ptr++;
+ }
+ if (flag_space) {
+ *format_ptr = ' ';
+ format_ptr++;
+ }
+ if (flag_sharp) {
+ *format_ptr = '#';
+ format_ptr++;
+ }
+ if (flag_zero) {
+ *format_ptr = '0';
+ format_ptr++;
+ } /* '0' *must* be the last one */
+
+ if (width != -1) {
+ sprintf(format_ptr, "%i", width);
+ format_ptr += strlen(format_ptr);
+ }
+
+ if (prec != -1) {
+ *format_ptr = '.';
+ format_ptr++;
+ sprintf(format_ptr, "%i", prec);
+ format_ptr += strlen(format_ptr);
+ }
+
+ if (modifier != -1) {
+ if (modifier == 'L' && strchr("diouxX",type) != NULL) {
+ *format_ptr = 'l';
+ format_ptr++;
+ *format_ptr = 'l';
+ format_ptr++;
+ } else {
+ *format_ptr = modifier;
+ format_ptr++;
+ }
+ }
+
+ *format_ptr = type;
+ format_ptr++;
+ *format_ptr = 0;
+
+ /* vague approximation of minimal length if width or prec are specified */
+ approx_width = width + prec;
+ if (approx_width < 0) /* because width == -1 and/or prec == -1 */
+ approx_width = 0;
+
+ switch (type) {
+ /* int */
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ switch (modifier) {
+ case -1 :
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, int));
+ case 'L':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, long long int));
+ case 'l':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, long int));
+ case 'h':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, int));
+ case 'z':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, size_t));
+ case 't':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, ptrdiff_t));
+ /* 'int' instead of 'short int' because default promotion is 'int' */
+ default:
+ INCOHERENT();
+ }
+
+ /* char */
+ case 'c':
+ if (modifier != -1)
+ INCOHERENT();
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, int));
+ /* 'int' instead of 'char' because default promotion is 'int' */
+
+ /* math */
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'E':
+ case 'G':
+ switch (modifier) {
+ case -1 : /* because of default promotion, no modifier means 'l' */
+ case 'l':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, double));
+ case 'L':
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, long double));
+ default:
+ INCOHERENT();
+ }
+
+ /* string */
+ case 's':
+ return type_s(s, width, prec, format_string, va_arg(s->vargs, const char*));
+
+ /* pointer */
+ case 'p':
+ if (modifier == -1)
+ return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, void *));
+ INCOHERENT();
+
+ /* store */
+ case 'n':
+ if (modifier == -1) {
+ int * p;
+ p = va_arg(s->vargs, int *);
+ if (p != NULL) {
+ *p = s->pseudo_len;
+ return 0;
+ }
+ return EOF;
+ }
+ INCOHERENT();
+
+ } /* switch */
+
+ INCOHERENT(); /* unknown type */
+
+#undef INCOHERENT
+#undef INCOHERENT_TEST
+#undef SRCTXT
+#undef DESTTXT
+}
+
+/*
+ * Return value: number of *virtually* written characters
+ * EOF = error
+ */
+static int core(xprintf_struct *s)
+{
+ size_t save_len;
+ char *dummy_base;
+
+ /* basic checks */
+ if ((int)(s->maxlen) <= 0) /* 'int' to check against some conversion */
+ return EOF; /* error for example if value is (int)-10 */
+ s->maxlen--; /* because initial maxlen counts final 0 */
+ /* note: now 'maxlen' _can_ be zero */
+
+ if (s->src_string == NULL)
+ s->src_string = "(null)";
+
+ /* struct init and memory allocation */
+ s->buffer_base = NULL;
+ s->buffer_len = 0;
+ s->real_len = 0;
+ s->pseudo_len = 0;
+ if (realloc_buff(s, (size_t)0) == EOF)
+ return EOF;
+ s->dest_string = s->buffer_base;
+
+ /* process source string */
+ for (;;) {
+ /* up to end of source string */
+ if (*(s->src_string) == 0) {
+ *(s->dest_string) = '\0'; /* final NUL */
+ break;
+ }
+
+ if (dispatch(s) == EOF)
+ goto free_EOF;
+
+ /* up to end of dest string */
+ if (s->real_len >= s->maxlen) {
+ (s->buffer_base)[s->maxlen] = '\0'; /* final NUL */
+ break;
+ }
+ }
+
+ /* for (v)asnprintf */
+ dummy_base = s->buffer_base + s->real_len;
+ save_len = s->real_len;
+
+ /* process the remaining of source string to compute 'pseudo_len'. We
+ * overwrite again and again, starting at 'dummy_base' because we don't
+ * need the text, only char count. */
+ while(*(s->src_string) != 0) { /* up to end of source string */
+ s->real_len = 0;
+ s->dest_string = dummy_base;
+ if (dispatch(s) == EOF)
+ goto free_EOF;
+ }
+
+ s->buffer_base = (char *)realloc((void *)(s->buffer_base), save_len + 1);
+ if (s->buffer_base == NULL)
+ return EOF; /* should rarely happen because we shrink the buffer */
+ return s->pseudo_len;
+
+ free_EOF:
+ free(s->buffer_base);
+ return EOF;
+}
+
+int vasprintf(char **ptr, const char *format_string, va_list vargs)
+{
+ xprintf_struct s;
+ int retval;
+
+ memset(&s, 0, sizeof(s));
+ s.src_string = format_string;
+#ifdef va_copy
+ va_copy (s.vargs, vargs);
+#else
+# ifdef __va_copy
+ __va_copy (s.vargs, vargs);
+# else
+# ifdef WIN32
+ s.vargs = vargs;
+# else
+ memcpy (&s.vargs, &vargs, sizeof (s.vargs));
+# endif /* WIN32 */
+# endif /* __va_copy */
+#endif /* va_copy */
+ s.maxlen = (size_t)INT_MAX;
+
+ retval = core(&s);
+ va_end(s.vargs);
+ if (retval == EOF) {
+ *ptr = NULL;
+ return EOF;
+ }
+
+ *ptr = s.buffer_base;
+ return retval;
+}