summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
commit6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch)
tree2e301d871bbeeb44aa57ff9cc070fcf3be484487 /frontend
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'frontend')
-rw-r--r--frontend/Makefile.am34
-rw-r--r--frontend/Makefile.in772
-rw-r--r--frontend/saned.c3355
-rw-r--r--frontend/scanimage.c2365
-rw-r--r--frontend/stiff.c610
-rw-r--r--frontend/stiff.h20
-rw-r--r--frontend/test.c182
-rw-r--r--frontend/tstbackend.c1871
8 files changed, 9209 insertions, 0 deletions
diff --git a/frontend/Makefile.am b/frontend/Makefile.am
new file mode 100644
index 0000000..a501931
--- /dev/null
+++ b/frontend/Makefile.am
@@ -0,0 +1,34 @@
+## Makefile.am -- an automake template for Makefile.in file
+## Copyright (C) 2009 Chris Bagwell and Sane Developers.
+##
+## This file is part of the "Sane" build infra-structure. See
+## included LICENSE file for license information.
+
+EXTRA_PROGRAMS = test tstbackend
+
+bin_PROGRAMS = scanimage
+
+if COMPILE_SANED
+sbin_PROGRAMS = saned
+else
+EXTRA_PROGRAMS += saned
+endif
+
+AM_CPPFLAGS = -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include
+
+scanimage_SOURCES = scanimage.c stiff.c stiff.h
+scanimage_LDADD = ../backend/libsane.la ../sanei/libsanei.la ../lib/liblib.la \
+ ../lib/libfelib.la
+
+saned_SOURCES = saned.c
+saned_LDADD = ../backend/libsane.la ../sanei/libsanei.la ../lib/liblib.la \
+ ../lib/libfelib.la @SYSLOG_LIBS@ @SYSTEMD_LIBS@
+
+test_SOURCES = test.c
+test_LDADD = ../lib/liblib.la ../lib/libfelib.la ../backend/libsane.la
+
+tstbackend_SOURCES = tstbackend.c
+tstbackend_LDADD = ../lib/liblib.la ../lib/libfelib.la ../backend/libsane.la
+
+clean-local:
+ rm -f test tstbackend
diff --git a/frontend/Makefile.in b/frontend/Makefile.in
new file mode 100644
index 0000000..18b9877
--- /dev/null
+++ b/frontend/Makefile.in
@@ -0,0 +1,772 @@
+# Makefile.in generated by automake 1.13.4 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 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 = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+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))
+pkgdatadir = $(datadir)/@PACKAGE@
+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@
+EXTRA_PROGRAMS = test$(EXEEXT) tstbackend$(EXEEXT) $(am__EXEEXT_1)
+bin_PROGRAMS = scanimage$(EXEEXT)
+@COMPILE_SANED_TRUE@sbin_PROGRAMS = saned$(EXEEXT)
+@COMPILE_SANED_FALSE@am__append_1 = saned
+subdir = frontend
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/mkinstalldirs $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/byteorder.m4 \
+ $(top_srcdir)/m4/stdint.m4 $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/include/sane/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@COMPILE_SANED_FALSE@am__EXEEXT_1 = saned$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)"
+PROGRAMS = $(bin_PROGRAMS) $(sbin_PROGRAMS)
+am_saned_OBJECTS = saned.$(OBJEXT)
+saned_OBJECTS = $(am_saned_OBJECTS)
+saned_DEPENDENCIES = ../backend/libsane.la ../sanei/libsanei.la \
+ ../lib/liblib.la ../lib/libfelib.la
+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 =
+am_scanimage_OBJECTS = scanimage.$(OBJEXT) stiff.$(OBJEXT)
+scanimage_OBJECTS = $(am_scanimage_OBJECTS)
+scanimage_DEPENDENCIES = ../backend/libsane.la ../sanei/libsanei.la \
+ ../lib/liblib.la ../lib/libfelib.la
+am_test_OBJECTS = test.$(OBJEXT)
+test_OBJECTS = $(am_test_OBJECTS)
+test_DEPENDENCIES = ../lib/liblib.la ../lib/libfelib.la \
+ ../backend/libsane.la
+am_tstbackend_OBJECTS = tstbackend.$(OBJEXT)
+tstbackend_OBJECTS = $(am_tstbackend_OBJECTS)
+tstbackend_DEPENDENCIES = ../lib/liblib.la ../lib/libfelib.la \
+ ../backend/libsane.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)/include/sane
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+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 = $(saned_SOURCES) $(scanimage_SOURCES) $(test_SOURCES) \
+ $(tstbackend_SOURCES)
+DIST_SOURCES = $(saned_SOURCES) $(scanimage_SOURCES) $(test_SOURCES) \
+ $(tstbackend_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+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
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AVAHI_CFLAGS = @AVAHI_CFLAGS@
+AVAHI_LIBS = @AVAHI_LIBS@
+AWK = @AWK@
+BACKENDS = @BACKENDS@
+BACKEND_CONFS_ENABLED = @BACKEND_CONFS_ENABLED@
+BACKEND_LIBS_ENABLED = @BACKEND_LIBS_ENABLED@
+BACKEND_MANS_ENABLED = @BACKEND_MANS_ENABLED@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCLEAN_FILES = @DISTCLEAN_FILES@
+DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DVIPS = @DVIPS@
+DYNAMIC_FLAG = @DYNAMIC_FLAG@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GPHOTO2_CPPFLAGS = @GPHOTO2_CPPFLAGS@
+GPHOTO2_LDFLAGS = @GPHOTO2_LDFLAGS@
+GPHOTO2_LIBS = @GPHOTO2_LIBS@
+GREP = @GREP@
+HAVE_GPHOTO2 = @HAVE_GPHOTO2@
+IEEE1284_LIBS = @IEEE1284_LIBS@
+INCLUDES = @INCLUDES@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_LOCKPATH = @INSTALL_LOCKPATH@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JPEG_LIBS = @JPEG_LIBS@
+LATEX = @LATEX@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUSB_1_0_CFLAGS = @LIBUSB_1_0_CFLAGS@
+LIBUSB_1_0_LIBS = @LIBUSB_1_0_LIBS@
+LIBV4L_CFLAGS = @LIBV4L_CFLAGS@
+LIBV4L_LIBS = @LIBV4L_LIBS@
+LINKER_RPATH = @LINKER_RPATH@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOCKPATH_GROUP = @LOCKPATH_GROUP@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINDEX = @MAKEINDEX@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MATH_LIB = @MATH_LIB@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NUMBER_VERSION = @NUMBER_VERSION@
+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@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PRELOADABLE_BACKENDS = @PRELOADABLE_BACKENDS@
+PRELOADABLE_BACKENDS_ENABLED = @PRELOADABLE_BACKENDS_ENABLED@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RESMGR_LIBS = @RESMGR_LIBS@
+SANEI_SANEI_JPEG_LO = @SANEI_SANEI_JPEG_LO@
+SANE_CONFIG_PATH = @SANE_CONFIG_PATH@
+SCSI_LIBS = @SCSI_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SNMP_CONFIG_PATH = @SNMP_CONFIG_PATH@
+SOCKET_LIBS = @SOCKET_LIBS@
+STRICT_LDFLAGS = @STRICT_LDFLAGS@
+STRIP = @STRIP@
+SYSLOG_LIBS = @SYSLOG_LIBS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+TIFF_LIBS = @TIFF_LIBS@
+USB_LIBS = @USB_LIBS@
+VERSION = @VERSION@
+V_MAJOR = @V_MAJOR@
+V_MINOR = @V_MINOR@
+V_REV = @V_REV@
+XGETTEXT = @XGETTEXT@
+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@
+configdir = @configdir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+effective_target = @effective_target@
+exec_prefix = @exec_prefix@
+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@
+locksanedir = @locksanedir@
+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@
+AM_CPPFLAGS = -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include
+scanimage_SOURCES = scanimage.c stiff.c stiff.h
+scanimage_LDADD = ../backend/libsane.la ../sanei/libsanei.la ../lib/liblib.la \
+ ../lib/libfelib.la
+
+saned_SOURCES = saned.c
+saned_LDADD = ../backend/libsane.la ../sanei/libsanei.la ../lib/liblib.la \
+ ../lib/libfelib.la @SYSLOG_LIBS@ @SYSTEMD_LIBS@
+
+test_SOURCES = test.c
+test_LDADD = ../lib/liblib.la ../lib/libfelib.la ../backend/libsane.la
+tstbackend_SOURCES = tstbackend.c
+tstbackend_LDADD = ../lib/liblib.la ../lib/libfelib.la ../backend/libsane.la
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(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) --gnu frontend/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu frontend/Makefile
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(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-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || 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)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || 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)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_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
+
+saned$(EXEEXT): $(saned_OBJECTS) $(saned_DEPENDENCIES) $(EXTRA_saned_DEPENDENCIES)
+ @rm -f saned$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(saned_OBJECTS) $(saned_LDADD) $(LIBS)
+
+scanimage$(EXEEXT): $(scanimage_OBJECTS) $(scanimage_DEPENDENCIES) $(EXTRA_scanimage_DEPENDENCIES)
+ @rm -f scanimage$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(scanimage_OBJECTS) $(scanimage_LDADD) $(LIBS)
+
+test$(EXEEXT): $(test_OBJECTS) $(test_DEPENDENCIES) $(EXTRA_test_DEPENDENCIES)
+ @rm -f test$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_OBJECTS) $(test_LDADD) $(LIBS)
+
+tstbackend$(EXEEXT): $(tstbackend_OBJECTS) $(tstbackend_DEPENDENCIES) $(EXTRA_tstbackend_DEPENDENCIES)
+ @rm -f tstbackend$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(tstbackend_OBJECTS) $(tstbackend_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/saned.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scanimage.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stiff.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tstbackend.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.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 $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.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 `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.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
+
+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: $(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: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: 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:
+
+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."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool clean-local \
+ clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -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-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS install-sbinPROGRAMS
+
+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 -rf ./$(DEPDIR)
+ -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-sbinPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libtool clean-local \
+ clean-sbinPROGRAMS 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-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-sbinPROGRAMS 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-sbinPROGRAMS
+
+
+clean-local:
+ rm -f test tstbackend
+
+# 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/frontend/saned.c b/frontend/saned.c
new file mode 100644
index 0000000..108512d
--- /dev/null
+++ b/frontend/saned.c
@@ -0,0 +1,3355 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 Andreas Beck
+ Copyright (C) 2001 - 2004 Henning Meier-Geinitz
+ Copyright (C) 2003, 2008 Julien BLACHE <jb@jblache.org>
+ AF-independent + IPv6 code, standalone mode
+
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ The SANE network daemon. This is the counterpart to the NET
+ backend.
+*/
+
+#ifdef _AIX
+# include "../include/lalloca.h" /* MUST come first for AIX! */
+#endif
+
+#include "../include/sane/config.h"
+#include "../include/lalloca.h"
+#include <sys/types.h>
+
+#if defined(HAVE_GETADDRINFO) && defined (HAVE_GETNAMEINFO)
+# define SANED_USES_AF_INDEP
+# ifdef HAS_SS_FAMILY
+# define SS_FAMILY(ss) ss.ss_family
+# elif defined(HAS___SS_FAMILY)
+# define SS_FAMILY(ss) ss.__ss_family
+# else /* fallback to the old, IPv4-only code */
+# undef SANED_USES_AF_INDEP
+# undef ENABLE_IPV6
+# endif
+#else
+# undef ENABLE_IPV6
+#endif /* HAVE_GETADDRINFO && HAVE_GETNAMEINFO */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+#ifdef HAVE_LIBC_H
+# include <libc.h> /* NeXTStep/OpenStep */
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include <netinet/in.h>
+
+#include <stdarg.h>
+
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <sys/wait.h>
+
+#include <pwd.h>
+#include <grp.h>
+
+#if defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL)
+# include <sys/poll.h>
+#else
+/*
+ * This replacement poll() using select() is only designed to cover
+ * our needs in run_standalone(). It should probably be extended...
+ */
+struct pollfd
+{
+ int fd;
+ short events;
+ short revents;
+};
+
+#define POLLIN 0x0001
+#define POLLERR 0x0002
+
+int
+poll (struct pollfd *ufds, unsigned int nfds, int timeout);
+
+int
+poll (struct pollfd *ufds, unsigned int nfds, int timeout)
+{
+ struct pollfd *fdp;
+
+ fd_set rfds;
+ fd_set efds;
+ struct timeval tv;
+ int maxfd = 0;
+ unsigned int i;
+ int ret;
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000;
+
+ FD_ZERO (&rfds);
+ FD_ZERO (&efds);
+
+ for (i = 0, fdp = ufds; i < nfds; i++, fdp++)
+ {
+ fdp->revents = 0;
+
+ if (fdp->events & POLLIN)
+ FD_SET (fdp->fd, &rfds);
+
+ FD_SET (fdp->fd, &efds);
+
+ maxfd = (fdp->fd > maxfd) ? fdp->fd : maxfd;
+ }
+
+ maxfd++;
+
+ ret = select (maxfd, &rfds, NULL, &efds, &tv);
+
+ if (ret < 0)
+ return ret;
+
+ for (i = 0, fdp = ufds; i < nfds; i++, fdp++)
+ {
+ if (fdp->events & POLLIN)
+ if (FD_ISSET (fdp->fd, &rfds))
+ fdp->revents |= POLLIN;
+
+ if (FD_ISSET (fdp->fd, &efds))
+ fdp->revents |= POLLERR;
+ }
+
+ return ret;
+}
+#endif /* HAVE_SYS_POLL_H && HAVE_POLL */
+
+#ifdef WITH_AVAHI
+# include <avahi-client/client.h>
+# include <avahi-client/publish.h>
+
+# include <avahi-common/alternative.h>
+# include <avahi-common/simple-watch.h>
+# include <avahi-common/malloc.h>
+# include <avahi-common/error.h>
+
+# define SANED_SERVICE_DNS "_sane-port._tcp"
+# define SANED_NAME "saned"
+
+pid_t avahi_pid = -1;
+
+char *avahi_svc_name;
+
+static AvahiClient *avahi_client = NULL;
+static AvahiSimplePoll *avahi_poll = NULL;
+static AvahiEntryGroup *avahi_group = NULL;
+#endif /* WITH_AVAHI */
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/sanei_net.h"
+#include "../include/sane/sanei_codec_bin.h"
+#include "../include/sane/sanei_config.h"
+
+#include "../include/sane/sanei_auth.h"
+
+#ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+#endif
+
+#ifndef IN_LOOPBACK
+# define IN_LOOPBACK(addr) (addr == 0x7f000001L)
+#endif
+
+#ifdef ENABLE_IPV6
+# define SANE_IN6_IS_ADDR_LOOPBACK(a) \
+ (((const uint32_t *) (a))[0] == 0 \
+ && ((const uint32_t *) (a))[1] == 0 \
+ && ((const uint32_t *) (a))[2] == 0 \
+ && ((const uint32_t *) (a))[3] == htonl (1))
+
+#define SANE_IN6_IS_ADDR_V4MAPPED(a) \
+((((const uint32_t *) (a))[0] == 0) \
+ && (((const uint32_t *) (a))[1] == 0) \
+ && (((const uint32_t *) (a))[2] == htonl (0xffff)))
+#endif /* ENABLE_IPV6 */
+
+#ifndef MAXHOSTNAMELEN
+# define MAXHOSTNAMELEN 120
+#endif
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+struct saned_child {
+ pid_t pid;
+ struct saned_child *next;
+};
+struct saned_child *children;
+int numchildren;
+
+#define SANED_CONFIG_FILE "saned.conf"
+#define SANED_PID_FILE "/var/run/saned.pid"
+
+#define SANED_SERVICE_NAME "sane-port"
+#define SANED_SERVICE_PORT 6566
+#define SANED_SERVICE_PORT_S "6566"
+
+typedef struct
+{
+ u_int inuse:1; /* is this handle in use? */
+ u_int scanning:1; /* are we scanning? */
+ u_int docancel:1; /* cancel the current scan */
+ SANE_Handle handle; /* backends handle */
+}
+Handle;
+
+static SANE_Net_Procedure_Number current_request;
+static const char *prog_name;
+static int can_authorize;
+static Wire wire;
+static int num_handles;
+static int debug;
+static int run_mode;
+static Handle *handle;
+static union
+{
+ int w;
+ u_char ch;
+}
+byte_order;
+
+/* The default-user name. This is not used to imply any rights. All
+ it does is save a remote user some work by reducing the amount of
+ text s/he has to type when authentication is requested. */
+static const char *default_username = "saned-user";
+static char *remote_ip;
+
+/* data port range */
+static in_port_t data_port_lo;
+static in_port_t data_port_hi;
+
+#ifdef SANED_USES_AF_INDEP
+static union {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 sin6;
+#endif
+} remote_address;
+static int remote_address_len;
+#else
+static struct in_addr remote_address;
+#endif /* SANED_USES_AF_INDEP */
+
+#ifndef _PATH_HEQUIV
+# define _PATH_HEQUIV "/etc/hosts.equiv"
+#endif
+
+static const char *config_file_names[] = {
+ _PATH_HEQUIV, SANED_CONFIG_FILE
+};
+
+static SANE_Bool log_to_syslog = SANE_TRUE;
+
+/* forward declarations: */
+static int process_request (Wire * w);
+
+#define SANED_RUN_INETD 0
+#define SANED_RUN_DEBUG 1
+#define SANED_RUN_ALONE 2
+
+
+#define DBG_ERR 1
+#define DBG_WARN 2
+#define DBG_MSG 3
+#define DBG_INFO 4
+#define DBG_DBG 5
+
+#define DBG saned_debug_call
+
+static void
+saned_debug_call (int level, const char *fmt, ...)
+{
+#ifndef NDEBUG
+ va_list ap;
+ va_start (ap, fmt);
+ if (debug >= level)
+ {
+ if (log_to_syslog)
+ {
+ /* print to syslog */
+ vsyslog (LOG_DEBUG, fmt, ap);
+ }
+ else
+ {
+ /* print to stderr */
+ fprintf (stderr, "[saned] ");
+ vfprintf (stderr, fmt, ap);
+ }
+ }
+ va_end (ap);
+#endif
+}
+
+
+static void
+reset_watchdog (void)
+{
+ if (!debug)
+ alarm (3600);
+}
+
+static void
+auth_callback (SANE_String_Const res,
+ SANE_Char *username,
+ SANE_Char *password)
+{
+ SANE_Net_Procedure_Number procnum;
+ SANE_Authorization_Req req;
+ SANE_Word word, ack = 0;
+
+ memset (username, 0, SANE_MAX_USERNAME_LEN);
+ memset (password, 0, SANE_MAX_PASSWORD_LEN);
+
+ if (!can_authorize)
+ {
+ DBG (DBG_WARN,
+ "auth_callback: called during non-authorizable RPC (resource=%s)\n",
+ res);
+ return;
+ }
+
+ if (wire.status)
+ {
+ DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
+ return;
+ }
+
+ switch (current_request)
+ {
+ case SANE_NET_OPEN:
+ {
+ SANE_Open_Reply reply;
+
+ memset (&reply, 0, sizeof (reply));
+ reply.resource_to_authorize = (char *) res;
+ sanei_w_reply (&wire, (WireCodecFunc) sanei_w_open_reply, &reply);
+ }
+ break;
+
+ case SANE_NET_CONTROL_OPTION:
+ {
+ SANE_Control_Option_Reply reply;
+
+ memset (&reply, 0, sizeof (reply));
+ reply.resource_to_authorize = (char *) res;
+ sanei_w_reply (&wire,
+ (WireCodecFunc) sanei_w_control_option_reply, &reply);
+ }
+ break;
+
+ case SANE_NET_START:
+ {
+ SANE_Start_Reply reply;
+
+ memset (&reply, 0, sizeof (reply));
+ reply.resource_to_authorize = (char *) res;
+ sanei_w_reply (&wire, (WireCodecFunc) sanei_w_start_reply, &reply);
+ }
+ break;
+
+ default:
+ DBG (DBG_WARN,
+ "auth_callback: called for unexpected request %d (resource=%s)\n",
+ current_request, res);
+ break;
+ }
+
+ if (wire.status)
+ {
+ DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
+ return;
+ }
+
+ reset_watchdog ();
+
+ sanei_w_set_dir (&wire, WIRE_DECODE);
+ sanei_w_word (&wire, &word);
+
+ if (wire.status)
+ {
+ DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
+ return;
+ }
+
+ procnum = word;
+ if (procnum != SANE_NET_AUTHORIZE)
+ {
+ DBG (DBG_WARN,
+ "auth_callback: bad procedure number %d "
+ "(expected: %d, resource=%s)\n", procnum, SANE_NET_AUTHORIZE,
+ res);
+ return;
+ }
+
+ sanei_w_authorization_req (&wire, &req);
+ if (wire.status)
+ {
+ DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
+ return;
+ }
+
+ if (req.username)
+ strcpy (username, req.username);
+ if (req.password)
+ strcpy (password, req.password);
+ if (!req.resource || strcmp (req.resource, res) != 0)
+ {
+ DBG (DBG_MSG,
+ "auth_callback: got auth for resource %s (expected resource=%s)\n",
+ res, req.resource);
+ }
+ sanei_w_free (&wire, (WireCodecFunc) sanei_w_authorization_req, &req);
+ sanei_w_reply (&wire, (WireCodecFunc) sanei_w_word, &ack);
+}
+
+static void
+quit (int signum)
+{
+ static int running = 0;
+ int i;
+
+ if (signum)
+ DBG (DBG_ERR, "quit: received signal %d\n", signum);
+
+ if (running)
+ {
+ DBG (DBG_ERR, "quit: already active, returning\n");
+ return;
+ }
+ running = 1;
+
+ for (i = 0; i < num_handles; ++i)
+ if (handle[i].inuse)
+ sane_close (handle[i].handle);
+
+ sane_exit ();
+ sanei_w_exit (&wire);
+ if (handle)
+ free (handle);
+ DBG (DBG_WARN, "quit: exiting\n");
+ if (log_to_syslog)
+ closelog ();
+ exit (EXIT_SUCCESS); /* This is a nowait-daemon. */
+}
+
+static SANE_Word
+get_free_handle (void)
+{
+# define ALLOC_INCREMENT 16
+ static int h, last_handle_checked = -1;
+
+ if (num_handles > 0)
+ {
+ h = last_handle_checked + 1;
+ do
+ {
+ if (h >= num_handles)
+ h = 0;
+ if (!handle[h].inuse)
+ {
+ last_handle_checked = h;
+ memset (handle + h, 0, sizeof (handle[0]));
+ handle[h].inuse = 1;
+ return h;
+ }
+ ++h;
+ }
+ while (h != last_handle_checked);
+ }
+
+ /* we're out of handles---alloc some more: */
+ last_handle_checked = num_handles - 1;
+ num_handles += ALLOC_INCREMENT;
+ if (handle)
+ handle = realloc (handle, num_handles * sizeof (handle[0]));
+ else
+ handle = malloc (num_handles * sizeof (handle[0]));
+ if (!handle)
+ return -1;
+ memset (handle + last_handle_checked + 1, 0,
+ ALLOC_INCREMENT * sizeof (handle[0]));
+ return get_free_handle ();
+# undef ALLOC_INCREMENT
+}
+
+static void
+close_handle (int h)
+{
+ if (h >= 0 && handle[h].inuse)
+ {
+ sane_close (handle[h].handle);
+ handle[h].inuse = 0;
+ }
+}
+
+static SANE_Word
+decode_handle (Wire * w, const char *op)
+{
+ SANE_Word h;
+
+ sanei_w_word (w, &h);
+ if (w->status || (unsigned) h >= (unsigned) num_handles || !handle[h].inuse)
+ {
+ DBG (DBG_ERR,
+ "decode_handle: %s: error while decoding handle argument "
+ "(h=%d, %s)\n", op, h, strerror (w->status));
+ return -1;
+ }
+ return h;
+}
+
+
+
+/* Convert a number of bits to an 8-bit bitmask */
+static unsigned int cidrtomask[9] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0,
+ 0xF8, 0xFC, 0xFE, 0xFF };
+
+#ifdef SANED_USES_AF_INDEP
+static SANE_Bool
+check_v4_in_range (struct sockaddr_in *sin, char *base_ip, char *netmask)
+{
+ int cidr;
+ int i, err;
+ char *end;
+ uint32_t mask;
+ struct sockaddr_in *base;
+ struct addrinfo hints;
+ struct addrinfo *res;
+ SANE_Bool ret = SANE_FALSE;
+
+ cidr = -1;
+ cidr = strtol (netmask, &end, 10);
+
+ /* Sanity check on the cidr value */
+ if ((cidr < 0) || (cidr > 32) || (end == netmask))
+ {
+ DBG (DBG_ERR, "check_v4_in_range: invalid CIDR value (%s) !\n", netmask);
+ return SANE_FALSE;
+ }
+
+ mask = 0;
+ cidr -= 8;
+
+ /* Build a bitmask out of the CIDR value */
+ for (i = 3; cidr >= 0; i--)
+ {
+ mask |= (0xff << (8 * i));
+ cidr -= 8;
+ }
+
+ if (cidr < 0)
+ mask |= (cidrtomask[cidr + 8] << (8 * i));
+
+ mask = htonl (mask);
+
+ /* get a sockaddr_in representing the base IP address */
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = PF_INET;
+
+ err = getaddrinfo (base_ip, NULL, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_DBG, "check_v4_in_range: getaddrinfo() failed: %s\n", gai_strerror (err));
+ return SANE_FALSE;
+ }
+
+ base = (struct sockaddr_in *) res->ai_addr;
+
+ /*
+ * Check that the address belongs to the specified subnet, using the bitmask.
+ * The address is represented by a 32bit integer.
+ */
+ if ((base->sin_addr.s_addr & mask) == (sin->sin_addr.s_addr & mask))
+ ret = SANE_TRUE;
+
+ freeaddrinfo (res);
+
+ return ret;
+}
+
+
+# ifdef ENABLE_IPV6
+
+static SANE_Bool
+check_v6_in_range (struct sockaddr_in6 *sin6, char *base_ip, char *netmask)
+{
+ int cidr;
+ int i, err;
+ unsigned int mask[16];
+ char *end;
+ struct sockaddr_in6 *base;
+ struct addrinfo hints;
+ struct addrinfo *res;
+ SANE_Bool ret = SANE_TRUE;
+
+ cidr = -1;
+ cidr = strtol (netmask, &end, 10);
+
+ /* Sanity check on the cidr value */
+ if ((cidr < 0) || (cidr > 128) || (end == netmask))
+ {
+ DBG (DBG_ERR, "check_v6_in_range: invalid CIDR value (%s) !\n", netmask);
+ return SANE_FALSE;
+ }
+
+ memset (mask, 0, (16 * sizeof (unsigned int)));
+ cidr -= 8;
+
+ /* Build a bitmask out of the CIDR value */
+ for (i = 0; cidr >= 0; i++)
+ {
+ mask[i] = 0xff;
+ cidr -= 8;
+ }
+
+ if (cidr < 0)
+ mask[i] = cidrtomask[cidr + 8];
+
+ /* get a sockaddr_in6 representing the base IP address */
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = PF_INET6;
+
+ err = getaddrinfo (base_ip, NULL, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_DBG, "check_v6_in_range: getaddrinfo() failed: %s\n", gai_strerror (err));
+ return SANE_FALSE;
+ }
+
+ base = (struct sockaddr_in6 *) res->ai_addr;
+
+ /*
+ * Check that the address belongs to the specified subnet.
+ * The address is reprensented by an array of 16 8bit integers.
+ */
+ for (i = 0; i < 16; i++)
+ {
+ if ((base->sin6_addr.s6_addr[i] & mask[i]) != (sin6->sin6_addr.s6_addr[i] & mask[i]))
+ {
+ ret = SANE_FALSE;
+ break;
+ }
+ }
+
+ freeaddrinfo (res);
+
+ return ret;
+}
+# endif /* ENABLE_IPV6 */
+#else /* !SANED_USES_AF_INDEP */
+static SANE_Bool
+check_v4_in_range (struct in_addr *inaddr, struct in_addr *base, char *netmask)
+{
+ int cidr;
+ int i;
+ char *end;
+ uint32_t mask;
+ SANE_Bool ret = SANE_FALSE;
+
+ cidr = -1;
+ cidr = strtol (netmask, &end, 10);
+
+ /* sanity check on the cidr value */
+ if ((cidr < 0) || (cidr > 32) || (end == netmask))
+ {
+ DBG (DBG_ERR, "check_v4_in_range: invalid CIDR value (%s) !\n", netmask);
+ return SANE_FALSE;
+ }
+
+ mask = 0;
+ cidr -= 8;
+
+ /* Build a bitmask out of the CIDR value */
+ for (i = 3; cidr >= 0; i--)
+ {
+ mask |= (0xff << (8 * i));
+ cidr -= 8;
+ }
+
+ if (cidr < 0)
+ mask |= (cidrtomask[cidr + 8] << (8 * i));
+
+ mask = htonl (mask);
+
+ /*
+ * Check that the address belongs to the specified subnet, using the bitmask.
+ * The address is represented by a 32bit integer.
+ */
+ if ((base->s_addr & mask) == (inaddr->s_addr & mask))
+ ret = SANE_TRUE;
+
+ return ret;
+}
+#endif /* SANED_USES_AF_INDEP */
+
+
+
+/* Access control */
+#ifdef SANED_USES_AF_INDEP
+static SANE_Status
+check_host (int fd)
+{
+ struct sockaddr_in *sin = NULL;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 *sin6;
+#endif /* ENABLE_IPV6 */
+ struct addrinfo hints;
+ struct addrinfo *res;
+ struct addrinfo *resp;
+ int j, access_ok = 0;
+ int err;
+ char text_addr[64];
+#ifdef ENABLE_IPV6
+ SANE_Bool IPv4map = SANE_FALSE;
+ char *remote_ipv4 = NULL; /* in case we have an IPv4-mapped address (eg ::ffff:127.0.0.1) */
+ char *tmp;
+ struct addrinfo *remote_ipv4_addr = NULL;
+#endif /* ENABLE_IPV6 */
+ char config_line_buf[1024];
+ char *config_line;
+ char *netmask;
+ char hostname[MAXHOSTNAMELEN];
+
+ int len;
+ FILE *fp;
+
+ /* Get address of remote host */
+ remote_address_len = sizeof (remote_address.ss);
+ if (getpeername (fd, &remote_address.sa, (socklen_t *) &remote_address_len) < 0)
+ {
+ DBG (DBG_ERR, "check_host: getpeername failed: %s\n", strerror (errno));
+ remote_ip = strdup ("[error]");
+ return SANE_STATUS_INVAL;
+ }
+
+ err = getnameinfo (&remote_address.sa, remote_address_len,
+ hostname, sizeof (hostname), NULL, 0, NI_NUMERICHOST);
+ if (err)
+ {
+ DBG (DBG_DBG, "check_host: getnameinfo failed: %s\n", gai_strerror(err));
+ remote_ip = strdup ("[error]");
+ return SANE_STATUS_INVAL;
+ }
+ else
+ remote_ip = strdup (hostname);
+
+#ifdef ENABLE_IPV6
+ sin6 = &remote_address.sin6;
+
+ if (SANE_IN6_IS_ADDR_V4MAPPED (sin6->sin6_addr.s6_addr))
+ {
+ DBG (DBG_DBG, "check_host: detected an IPv4-mapped address\n");
+ remote_ipv4 = remote_ip + 7;
+ IPv4map = SANE_TRUE;
+
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = PF_INET;
+
+ err = getaddrinfo (remote_ipv4, NULL, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_DBG, "check_host: getaddrinfo() failed: %s\n", gai_strerror (err));
+ IPv4map = SANE_FALSE; /* we failed, remote_ipv4_addr points to nothing */
+ }
+ else
+ {
+ remote_ipv4_addr = res;
+ sin = (struct sockaddr_in *)res->ai_addr;
+ }
+ }
+#endif /* ENABLE_IPV6 */
+
+ DBG (DBG_WARN, "check_host: access by remote host: %s\n", remote_ip);
+
+ /* Always allow access from local host. Do it here to avoid DNS lookups
+ and reading saned.conf. */
+
+#ifdef ENABLE_IPV6
+ if (IPv4map == SANE_TRUE)
+ {
+ if (IN_LOOPBACK (ntohl (sin->sin_addr.s_addr)))
+ {
+ DBG (DBG_MSG,
+ "check_host: remote host is IN_LOOPBACK: access granted\n");
+ freeaddrinfo (remote_ipv4_addr);
+ return SANE_STATUS_GOOD;
+ }
+ freeaddrinfo (remote_ipv4_addr);
+ }
+#endif /* ENABLE_IPV6 */
+
+ sin = &remote_address.sin;
+
+ switch (SS_FAMILY(remote_address.ss))
+ {
+ case AF_INET:
+ if (IN_LOOPBACK (ntohl (sin->sin_addr.s_addr)))
+ {
+ DBG (DBG_MSG,
+ "check_host: remote host is IN_LOOPBACK: access granted\n");
+ return SANE_STATUS_GOOD;
+ }
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ if (SANE_IN6_IS_ADDR_LOOPBACK (sin6->sin6_addr.s6_addr))
+ {
+ DBG (DBG_MSG,
+ "check_host: remote host is IN6_LOOPBACK: access granted\n");
+ return SANE_STATUS_GOOD;
+ }
+ break;
+#endif /* ENABLE_IPV6 */
+ default:
+ break;
+ }
+
+ DBG (DBG_DBG, "check_host: remote host is not IN_LOOPBACK"
+#ifdef ENABLE_IPV6
+ " nor IN6_LOOPBACK"
+#endif /* ENABLE_IPV6 */
+ "\n");
+
+
+ /* Get name of local host */
+ if (gethostname (hostname, sizeof (hostname)) < 0)
+ {
+ DBG (DBG_ERR, "check_host: gethostname failed: %s\n", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ DBG (DBG_DBG, "check_host: local hostname: %s\n", hostname);
+
+ /* Get local addresses */
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+#ifdef ENABLE_IPV6
+ hints.ai_family = PF_UNSPEC;
+#else
+ hints.ai_family = PF_INET;
+#endif /* ENABLE_IPV6 */
+
+ err = getaddrinfo (hostname, NULL, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_ERR, "check_host: getaddrinfo for local hostname failed: %s\n",
+ gai_strerror (err));
+
+ /* Proceed even if the local hostname does not resolve */
+ if (err != EAI_NONAME)
+ return SANE_STATUS_INVAL;
+ }
+ else
+ {
+ for (resp = res; resp != NULL; resp = resp->ai_next)
+ {
+ DBG (DBG_DBG, "check_host: local hostname(s) (from DNS): %s\n",
+ resp->ai_canonname);
+
+ err = getnameinfo (resp->ai_addr, resp->ai_addrlen, text_addr,
+ sizeof (text_addr), NULL, 0, NI_NUMERICHOST);
+ if (err)
+ strncpy (text_addr, "[error]", 8);
+
+#ifdef ENABLE_IPV6
+ if ((strcasecmp (text_addr, remote_ip) == 0) ||
+ ((IPv4map == SANE_TRUE) && (strcmp (text_addr, remote_ipv4) == 0)))
+#else
+ if (strcmp (text_addr, remote_ip) == 0)
+#endif /* ENABLE_IPV6 */
+ {
+ DBG (DBG_MSG, "check_host: remote host has same addr as local: access granted\n");
+
+ freeaddrinfo (res);
+ res = NULL;
+
+ return SANE_STATUS_GOOD;
+ }
+ }
+
+ freeaddrinfo (res);
+ res = NULL;
+
+ DBG (DBG_DBG,
+ "check_host: remote host doesn't have same addr as local\n");
+ }
+
+ /* must be a remote host: check contents of PATH_NET_CONFIG or
+ /etc/hosts.equiv if former doesn't exist: */
+ for (j = 0; j < NELEMS (config_file_names); ++j)
+ {
+ DBG (DBG_DBG, "check_host: opening config file: %s\n",
+ config_file_names[j]);
+ if (config_file_names[j][0] == '/')
+ fp = fopen (config_file_names[j], "r");
+ else
+ fp = sanei_config_open (config_file_names[j]);
+ if (!fp)
+ {
+ DBG (DBG_MSG,
+ "check_host: can't open config file: %s (%s)\n",
+ config_file_names[j], strerror (errno));
+ continue;
+ }
+
+ while (!access_ok && sanei_config_read (config_line_buf,
+ sizeof (config_line_buf), fp))
+ {
+ config_line = config_line_buf; /* from now on, use a pointer */
+ DBG (DBG_DBG, "check_host: config file line: `%s'\n", config_line);
+ if (config_line[0] == '#')
+ continue; /* ignore comments */
+
+ if (strchr (config_line, '='))
+ continue; /* ignore lines with an = sign */
+
+ len = strlen (config_line);
+ if (!len)
+ continue; /* ignore empty lines */
+
+ /* look for a subnet specification */
+ netmask = strchr (config_line, '/');
+ if (netmask != NULL)
+ {
+ *netmask = '\0';
+ netmask++;
+ DBG (DBG_DBG, "check_host: subnet with base IP = %s, CIDR netmask = %s\n",
+ config_line, netmask);
+ }
+
+#ifdef ENABLE_IPV6
+ /* IPv6 addresses are enclosed in [] */
+ if (*config_line == '[')
+ {
+ config_line++;
+ tmp = strchr (config_line, ']');
+ if (tmp == NULL)
+ {
+ DBG (DBG_ERR,
+ "check_host: malformed IPv6 address in config file, skipping: [%s\n",
+ config_line);
+ continue;
+ }
+ *tmp = '\0';
+ }
+#endif /* ENABLE_IPV6 */
+
+ if (strcmp (config_line, "+") == 0)
+ {
+ access_ok = 1;
+ DBG (DBG_DBG,
+ "check_host: access granted from any host (`+')\n");
+ }
+ /* compare remote_ip (remote IP address) to the config_line */
+ else if (strcasecmp (config_line, remote_ip) == 0)
+ {
+ access_ok = 1;
+ DBG (DBG_DBG,
+ "check_host: access granted from IP address %s\n", remote_ip);
+ }
+#ifdef ENABLE_IPV6
+ else if ((IPv4map == SANE_TRUE) && (strcmp (config_line, remote_ipv4) == 0))
+ {
+ access_ok = 1;
+ DBG (DBG_DBG,
+ "check_host: access granted from IP address %s (IPv4-mapped)\n", remote_ip);
+ }
+ /* handle IP ranges, take care of the IPv4map stuff */
+ else if (netmask != NULL)
+ {
+ if (strchr (config_line, ':') != NULL) /* is a v6 address */
+ {
+ if (SS_FAMILY(remote_address.ss) == AF_INET6)
+ {
+ if (check_v6_in_range (sin6, config_line, netmask))
+ {
+ access_ok = 1;
+ DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet [%s]/%s)\n",
+ remote_ip, config_line, netmask);
+ }
+ }
+ }
+ else /* is a v4 address */
+ {
+ if (IPv4map == SANE_TRUE)
+ {
+ /* get a sockaddr_in representing the v4-mapped IP address */
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = PF_INET;
+
+ err = getaddrinfo (remote_ipv4, NULL, &hints, &res);
+ if (err)
+ DBG (DBG_DBG, "check_host: getaddrinfo() failed: %s\n", gai_strerror (err));
+ else
+ sin = (struct sockaddr_in *)res->ai_addr;
+ }
+
+ if ((SS_FAMILY(remote_address.ss) == AF_INET) ||
+ (IPv4map == SANE_TRUE))
+ {
+
+ if (check_v4_in_range (sin, config_line, netmask))
+ {
+ DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet %s/%s)\n",
+ ((IPv4map == SANE_TRUE) ? remote_ipv4 : remote_ip), config_line, netmask);
+ access_ok = 1;
+ }
+ else
+ {
+ /* restore the old sin pointer */
+ sin = &remote_address.sin;
+ }
+
+ if (res != NULL)
+ {
+ freeaddrinfo (res);
+ res = NULL;
+ }
+ }
+ }
+ }
+#else /* !ENABLE_IPV6 */
+ /* handle IP ranges */
+ else if (netmask != NULL)
+ {
+ if (check_v4_in_range (sin, config_line, netmask))
+ {
+ access_ok = 1;
+ DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet %s/%s)\n",
+ remote_ip, config_line, netmask);
+ }
+ }
+#endif /* ENABLE_IPV6 */
+ else
+ {
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+#ifdef ENABLE_IPV6
+ hints.ai_family = PF_UNSPEC;
+#else
+ hints.ai_family = PF_INET;
+#endif /* ENABLE_IPV6 */
+
+ err = getaddrinfo (config_line, NULL, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_DBG,
+ "check_host: getaddrinfo for `%s' failed: %s\n",
+ config_line, gai_strerror (err));
+ DBG (DBG_MSG, "check_host: entry isn't an IP address "
+ "and can't be found in DNS\n");
+ continue;
+ }
+ else
+ {
+ for (resp = res; resp != NULL; resp = resp->ai_next)
+ {
+ err = getnameinfo (resp->ai_addr, resp->ai_addrlen, text_addr,
+ sizeof (text_addr), NULL, 0, NI_NUMERICHOST);
+ if (err)
+ strncpy (text_addr, "[error]", 8);
+
+ DBG (DBG_MSG,
+ "check_host: DNS lookup returns IP address: %s\n",
+ text_addr);
+
+#ifdef ENABLE_IPV6
+ if ((strcasecmp (text_addr, remote_ip) == 0) ||
+ ((IPv4map == SANE_TRUE) && (strcmp (text_addr, remote_ipv4) == 0)))
+#else
+ if (strcmp (text_addr, remote_ip) == 0)
+#endif /* ENABLE_IPV6 */
+ access_ok = 1;
+
+ if (access_ok)
+ break;
+ }
+ freeaddrinfo (res);
+ res = NULL;
+ }
+ }
+ }
+ fclose (fp);
+ }
+
+ if (access_ok)
+ return SANE_STATUS_GOOD;
+
+ return SANE_STATUS_ACCESS_DENIED;
+}
+
+#else /* !SANED_USES_AF_INDEP */
+
+static SANE_Status
+check_host (int fd)
+{
+ struct sockaddr_in sin;
+ int j, access_ok = 0;
+ struct hostent *he;
+ char text_addr[64];
+ char config_line_buf[1024];
+ char *config_line;
+ char *netmask;
+ char hostname[MAXHOSTNAMELEN];
+ char *r_hostname;
+ static struct in_addr config_line_address;
+
+ int len;
+ FILE *fp;
+
+ /* Get address of remote host */
+ len = sizeof (sin);
+ if (getpeername (fd, (struct sockaddr *) &sin, (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "check_host: getpeername failed: %s\n", strerror (errno));
+ remote_ip = strdup ("[error]");
+ return SANE_STATUS_INVAL;
+ }
+ r_hostname = inet_ntoa (sin.sin_addr);
+ remote_ip = strdup (r_hostname);
+ DBG (DBG_WARN, "check_host: access by remote host: %s\n",
+ remote_ip);
+ /* Save remote address for check of control and data connections */
+ memcpy (&remote_address, &sin.sin_addr, sizeof (remote_address));
+
+ /* Always allow access from local host. Do it here to avoid DNS lookups
+ and reading saned.conf. */
+ if (IN_LOOPBACK (ntohl (sin.sin_addr.s_addr)))
+ {
+ DBG (DBG_MSG,
+ "check_host: remote host is IN_LOOPBACK: access accepted\n");
+ return SANE_STATUS_GOOD;
+ }
+ DBG (DBG_DBG, "check_host: remote host is not IN_LOOPBACK\n");
+
+ /* Get name of local host */
+ if (gethostname (hostname, sizeof (hostname)) < 0)
+ {
+ DBG (DBG_ERR, "check_host: gethostname failed: %s\n", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ DBG (DBG_DBG, "check_host: local hostname: %s\n", hostname);
+
+ /* Get local address */
+ he = gethostbyname (hostname);
+
+ if (!he)
+ {
+ DBG (DBG_ERR, "check_host: gethostbyname for local hostname failed: %s\n",
+ hstrerror (h_errno));
+
+ /* Proceed even if the local hostname doesn't resolve */
+ if (h_errno != HOST_NOT_FOUND)
+ return SANE_STATUS_INVAL;
+ }
+ else
+ {
+ DBG (DBG_DBG, "check_host: local hostname (from DNS): %s\n",
+ he->h_name);
+
+ if ((he->h_length == 4) || (he->h_addrtype == AF_INET))
+ {
+ if (!inet_ntop (he->h_addrtype, he->h_addr_list[0], text_addr,
+ sizeof (text_addr)))
+ strcpy (text_addr, "[error]");
+ DBG (DBG_DBG, "check_host: local host address (from DNS): %s\n",
+ text_addr);
+ if (memcmp (he->h_addr_list[0], &remote_address.s_addr, 4) == 0)
+ {
+ DBG (DBG_MSG,
+ "check_host: remote host has same addr as local: "
+ "access accepted\n");
+ return SANE_STATUS_GOOD;
+ }
+ }
+ else
+ {
+ DBG (DBG_ERR, "check_host: can't get local address "
+ "(only IPv4 is supported)\n");
+ }
+
+ DBG (DBG_DBG,
+ "check_host: remote host doesn't have same addr as local\n");
+ }
+
+ /* must be a remote host: check contents of PATH_NET_CONFIG or
+ /etc/hosts.equiv if former doesn't exist: */
+ for (j = 0; j < NELEMS (config_file_names); ++j)
+ {
+ DBG (DBG_DBG, "check_host: opening config file: %s\n",
+ config_file_names[j]);
+ if (config_file_names[j][0] == '/')
+ fp = fopen (config_file_names[j], "r");
+ else
+ fp = sanei_config_open (config_file_names[j]);
+ if (!fp)
+ {
+ DBG (DBG_MSG,
+ "check_host: can't open config file: %s (%s)\n",
+ config_file_names[j], strerror (errno));
+ continue;
+ }
+
+ while (!access_ok && sanei_config_read (config_line_buf,
+ sizeof (config_line_buf), fp))
+ {
+ config_line = config_line_buf; /* from now on, use a pointer */
+ DBG (DBG_DBG, "check_host: config file line: `%s'\n", config_line);
+ if (config_line[0] == '#')
+ continue; /* ignore comments */
+
+ if (strchr (config_line, '='))
+ continue; /* ignore lines with an = sign */
+
+ len = strlen (config_line);
+ if (!len)
+ continue; /* ignore empty lines */
+
+ /* look for a subnet specification */
+ netmask = strchr (config_line, '/');
+ if (netmask != NULL)
+ {
+ *netmask = '\0';
+ netmask++;
+ DBG (DBG_DBG, "check_host: subnet with base IP = %s, CIDR netmask = %s\n",
+ config_line, netmask);
+ }
+
+ if (strcmp (config_line, "+") == 0)
+ {
+ access_ok = 1;
+ DBG (DBG_DBG,
+ "check_host: access accepted from any host (`+')\n");
+ }
+ else
+ {
+ if (inet_pton (AF_INET, config_line, &config_line_address) > 0)
+ {
+ if (memcmp (&remote_address.s_addr,
+ &config_line_address.s_addr, 4) == 0)
+ access_ok = 1;
+ else if (netmask != NULL)
+ {
+ if (check_v4_in_range (&remote_address, &config_line_address, netmask))
+ {
+ access_ok = 1;
+ DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet %s/%s)\n",
+ remote_ip, config_line, netmask);
+ }
+ }
+ }
+ else
+ {
+ DBG (DBG_DBG,
+ "check_host: inet_pton for `%s' failed\n",
+ config_line);
+ he = gethostbyname (config_line);
+ if (!he)
+ {
+ DBG (DBG_DBG,
+ "check_host: gethostbyname for `%s' failed: %s\n",
+ config_line, hstrerror (h_errno));
+ DBG (DBG_MSG, "check_host: entry isn't an IP address "
+ "and can't be found in DNS\n");
+ continue;
+ }
+ if (!inet_ntop (he->h_addrtype, he->h_addr_list[0],
+ text_addr, sizeof (text_addr)))
+ strcpy (text_addr, "[error]");
+ DBG (DBG_MSG,
+ "check_host: DNS lookup returns IP address: %s\n",
+ text_addr);
+ if (memcmp (&remote_address.s_addr,
+ he->h_addr_list[0], 4) == 0)
+ access_ok = 1;
+ }
+ }
+ }
+ fclose (fp);
+ if (access_ok)
+ return SANE_STATUS_GOOD;
+ }
+ return SANE_STATUS_ACCESS_DENIED;
+}
+
+#endif /* SANED_USES_AF_INDEP */
+
+static int
+init (Wire * w)
+{
+ SANE_Word word, be_version_code;
+ SANE_Init_Reply reply;
+ SANE_Status status;
+ SANE_Init_Req req;
+
+ reset_watchdog ();
+
+ status = check_host (w->io.fd);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (DBG_WARN, "init: access by host %s denied\n", remote_ip);
+ return -1;
+ }
+ else
+ DBG (DBG_MSG, "init: access granted\n");
+
+ sanei_w_set_dir (w, WIRE_DECODE);
+ if (w->status)
+ {
+ DBG (DBG_ERR, "init: bad status after sanei_w_set_dir: %d\n", w->status);
+ return -1;
+ }
+
+ sanei_w_word (w, &word); /* decode procedure number */
+ if (w->status || word != SANE_NET_INIT)
+ {
+ DBG (DBG_ERR, "init: bad status=%d or procnum=%d\n",
+ w->status, word);
+ return -1;
+ }
+
+ sanei_w_init_req (w, &req);
+ if (w->status)
+ {
+ DBG (DBG_ERR, "init: bad status after sanei_w_init_req: %d\n", w->status);
+ return -1;
+ }
+
+ w->version = SANEI_NET_PROTOCOL_VERSION;
+ if (req.username)
+ default_username = strdup (req.username);
+
+ sanei_w_free (w, (WireCodecFunc) sanei_w_init_req, &req);
+ if (w->status)
+ {
+ DBG (DBG_ERR, "init: bad status after sanei_w_free: %d\n", w->status);
+ return -1;
+ }
+
+ reply.version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR,
+ SANEI_NET_PROTOCOL_VERSION);
+
+ DBG (DBG_WARN, "init: access granted to %s@%s\n",
+ default_username, remote_ip);
+
+ if (status == SANE_STATUS_GOOD)
+ {
+ status = sane_init (&be_version_code, auth_callback);
+ if (status != SANE_STATUS_GOOD)
+ DBG (DBG_ERR, "init: failed to initialize backend (%s)\n",
+ sane_strstatus (status));
+
+ if (SANE_VERSION_MAJOR (be_version_code) != V_MAJOR)
+ {
+ DBG (DBG_ERR,
+ "init: unexpected backend major version %d (expected %d)\n",
+ SANE_VERSION_MAJOR (be_version_code), V_MAJOR);
+ status = SANE_STATUS_INVAL;
+ }
+ }
+ reply.status = status;
+ if (status != SANE_STATUS_GOOD)
+ reply.version_code = 0;
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_init_reply, &reply);
+
+ if (w->status || status != SANE_STATUS_GOOD)
+ return -1;
+
+ return 0;
+}
+
+#ifdef SANED_USES_AF_INDEP
+static int
+start_scan (Wire * w, int h, SANE_Start_Reply * reply)
+{
+ union {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 sin6;
+#endif /* ENABLE_IPV6 */
+ } data_addr;
+ struct sockaddr_in *sin;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 *sin6;
+#endif /* ENABLE_IPV6 */
+ SANE_Handle be_handle;
+ int fd, len;
+ in_port_t data_port;
+ int ret;
+
+ be_handle = handle[h].handle;
+
+ len = sizeof (data_addr.ss);
+ if (getsockname (w->io.fd, &data_addr.sa, (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ fd = socket (SS_FAMILY(data_addr.ss), SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to obtain data socket (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ switch (SS_FAMILY(data_addr.ss))
+ {
+ case AF_INET:
+ sin = &data_addr.sin;
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ sin6 = &data_addr.sin6;
+ break;
+#endif /* ENABLE_IPV6 */
+ default:
+ break;
+ }
+
+ /* Try to bind a port between data_port_lo and data_port_hi for the data connection */
+ for (data_port = data_port_lo; data_port <= data_port_hi; data_port++)
+ {
+ switch (SS_FAMILY(data_addr.ss))
+ {
+ case AF_INET:
+ sin->sin_port = htons(data_port);
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ sin6->sin6_port = htons(data_port);
+ break;
+#endif /* ENABLE_IPV6 */
+ default:
+ break;
+ }
+
+ DBG (DBG_INFO, "start_scan: trying to bind data port %d\n", data_port);
+
+ ret = bind (fd, &data_addr.sa, len);
+ if (ret == 0)
+ break;
+ }
+
+ if (ret < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to bind address (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ if (listen (fd, 1) < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to make socket listen (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ if (getsockname (fd, &data_addr.sa, (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ switch (SS_FAMILY(data_addr.ss))
+ {
+ case AF_INET:
+ sin = &data_addr.sin;
+ reply->port = ntohs (sin->sin_port);
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ sin6 = &data_addr.sin6;
+ reply->port = ntohs (sin6->sin6_port);
+ break;
+#endif /* ENABLE_IPV6 */
+ default:
+ break;
+ }
+
+ DBG (DBG_MSG, "start_scan: using port %d for data\n", reply->port);
+
+ reply->status = sane_start (be_handle);
+ if (reply->status == SANE_STATUS_GOOD)
+ {
+ handle[h].scanning = 1;
+ handle[h].docancel = 0;
+ }
+
+ return fd;
+}
+
+#else /* !SANED_USES_AF_INDEP */
+
+static int
+start_scan (Wire * w, int h, SANE_Start_Reply * reply)
+{
+ struct sockaddr_in sin;
+ SANE_Handle be_handle;
+ int fd, len;
+ in_port_t data_port;
+ int ret;
+
+ be_handle = handle[h].handle;
+
+ len = sizeof (sin);
+ if (getsockname (w->io.fd, (struct sockaddr *) &sin, (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ fd = socket (AF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to obtain data socket (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ /* Try to bind a port between data_port_lo and data_port_hi for the data connection */
+ for (data_port = data_port_lo; data_port <= data_port_hi; data_port++)
+ {
+ sin.sin_port = htons(data_port);
+
+ DBG(DBG_INFO, "start_scan: trying to bind data port %d\n", data_port);
+
+ ret = bind (fd, (struct sockaddr *) &sin, len);
+ if (ret == 0)
+ break;
+ }
+
+ if (ret < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to bind address (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ if (listen (fd, 1) < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to make socket listen (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ if (getsockname (fd, (struct sockaddr *) &sin, (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
+ strerror (errno));
+ reply->status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ reply->port = ntohs (sin.sin_port);
+
+ DBG (DBG_MSG, "start_scan: using port %d for data\n", reply->port);
+
+ reply->status = sane_start (be_handle);
+ if (reply->status == SANE_STATUS_GOOD)
+ {
+ handle[h].scanning = 1;
+ handle[h].docancel = 0;
+ }
+
+ return fd;
+}
+#endif /* SANED_USES_AF_INDEP */
+
+static int
+store_reclen (SANE_Byte * buf, size_t buf_size, int i, size_t reclen)
+{
+ buf[i++] = (reclen >> 24) & 0xff;
+ if (i >= (int) buf_size)
+ i = 0;
+ buf[i++] = (reclen >> 16) & 0xff;
+ if (i >= (int) buf_size)
+ i = 0;
+ buf[i++] = (reclen >> 8) & 0xff;
+ if (i >= (int) buf_size)
+ i = 0;
+ buf[i++] = (reclen >> 0) & 0xff;
+ if (i >= (int) buf_size)
+ i = 0;
+ return i;
+}
+
+static void
+do_scan (Wire * w, int h, int data_fd)
+{
+ int num_fds, be_fd = -1, reader, writer, bytes_in_buf, status_dirty = 0;
+ SANE_Handle be_handle = handle[h].handle;
+ struct timeval tv, *timeout = 0;
+ fd_set rd_set, rd_mask, wr_set, wr_mask;
+ SANE_Byte buf[8192];
+ SANE_Status status;
+ long int nwritten;
+ SANE_Int length;
+ size_t nbytes;
+
+ DBG (3, "do_scan: start\n");
+
+ FD_ZERO (&rd_mask);
+ FD_SET (w->io.fd, &rd_mask);
+ num_fds = w->io.fd + 1;
+
+ FD_ZERO (&wr_mask);
+ FD_SET (data_fd, &wr_mask);
+ if (data_fd >= num_fds)
+ num_fds = data_fd + 1;
+
+ sane_set_io_mode (be_handle, SANE_TRUE);
+ if (sane_get_select_fd (be_handle, &be_fd) == SANE_STATUS_GOOD)
+ {
+ FD_SET (be_fd, &rd_mask);
+ if (be_fd >= num_fds)
+ num_fds = be_fd + 1;
+ }
+ else
+ {
+ memset (&tv, 0, sizeof (tv));
+ timeout = &tv;
+ }
+
+ status = SANE_STATUS_GOOD;
+ reader = writer = bytes_in_buf = 0;
+ do
+ {
+ rd_set = rd_mask;
+ wr_set = wr_mask;
+ if (select (num_fds, &rd_set, &wr_set, 0, timeout) < 0)
+ {
+ if (be_fd >= 0 && errno == EBADF)
+ {
+ /* This normally happens when a backend closes a select
+ filedescriptor when reaching the end of file. So
+ pass back this status to the client: */
+ FD_CLR (be_fd, &rd_mask);
+ be_fd = -1;
+ /* only set status_dirty if EOF hasn't been already detected */
+ if (status == SANE_STATUS_GOOD)
+ status_dirty = 1;
+ status = SANE_STATUS_EOF;
+ DBG (DBG_INFO, "do_scan: select_fd was closed --> EOF\n");
+ continue;
+ }
+ else
+ {
+ status = SANE_STATUS_IO_ERROR;
+ DBG (DBG_ERR, "do_scan: select failed (%s)\n", strerror (errno));
+ break;
+ }
+ }
+
+ if (bytes_in_buf)
+ {
+ if (FD_ISSET (data_fd, &wr_set))
+ {
+ if (bytes_in_buf > 0)
+ {
+ /* write more input data */
+ nbytes = bytes_in_buf;
+ if (writer + nbytes > sizeof (buf))
+ nbytes = sizeof (buf) - writer;
+ DBG (DBG_INFO,
+ "do_scan: trying to write %d bytes to client\n",
+ nbytes);
+ nwritten = write (data_fd, buf + writer, nbytes);
+ DBG (DBG_INFO,
+ "do_scan: wrote %ld bytes to client\n", nwritten);
+ if (nwritten < 0)
+ {
+ DBG (DBG_ERR, "do_scan: write failed (%s)\n",
+ strerror (errno));
+ status = SANE_STATUS_CANCELLED;
+ break;
+ }
+ bytes_in_buf -= nwritten;
+ writer += nwritten;
+ if (writer == sizeof (buf))
+ writer = 0;
+ }
+ }
+ }
+ else if (status == SANE_STATUS_GOOD
+ && (timeout || FD_ISSET (be_fd, &rd_set)))
+ {
+ int i;
+
+ /* get more input data */
+
+ /* reserve 4 bytes to store the length of the data record: */
+ i = reader;
+ reader += 4;
+ if (reader >= (int) sizeof (buf))
+ reader -= sizeof(buf);
+
+ assert (bytes_in_buf == 0);
+ nbytes = sizeof (buf) - 4;
+ if (reader + nbytes > sizeof (buf))
+ nbytes = sizeof (buf) - reader;
+
+ DBG (DBG_INFO,
+ "do_scan: trying to read %d bytes from scanner\n", nbytes);
+ status = sane_read (be_handle, buf + reader, nbytes, &length);
+ DBG (DBG_INFO,
+ "do_scan: read %d bytes from scanner\n", length);
+
+ reset_watchdog ();
+
+ reader += length;
+ if (reader >= (int) sizeof (buf))
+ reader = 0;
+ bytes_in_buf += length + 4;
+
+ if (status != SANE_STATUS_GOOD)
+ {
+ reader = i; /* restore reader index */
+ status_dirty = 1;
+ DBG (DBG_MSG,
+ "do_scan: status = `%s'\n", sane_strstatus(status));
+ }
+ else
+ store_reclen (buf, sizeof (buf), i, length);
+ }
+
+ if (status_dirty && sizeof (buf) - bytes_in_buf >= 5)
+ {
+ status_dirty = 0;
+ reader = store_reclen (buf, sizeof (buf), reader, 0xffffffff);
+ buf[reader] = status;
+ bytes_in_buf += 5;
+ DBG (DBG_MSG, "do_scan: statuscode `%s' was added to buffer\n",
+ sane_strstatus(status));
+ }
+
+ if (FD_ISSET (w->io.fd, &rd_set))
+ {
+ DBG (DBG_MSG,
+ "do_scan: processing RPC request on fd %d\n", w->io.fd);
+ process_request (w);
+ if (handle[h].docancel)
+ break;
+ }
+ }
+ while (status == SANE_STATUS_GOOD || bytes_in_buf > 0 || status_dirty);
+ DBG (DBG_MSG, "do_scan: done, status=%s\n", sane_strstatus (status));
+ handle[h].docancel = 0;
+ handle[h].scanning = 0;
+}
+
+static int
+process_request (Wire * w)
+{
+ SANE_Handle be_handle;
+ SANE_Word h, word;
+ int i;
+
+ DBG (DBG_DBG, "process_request: waiting for request\n");
+ sanei_w_set_dir (w, WIRE_DECODE);
+ sanei_w_word (w, &word); /* decode procedure number */
+
+ if (w->status)
+ {
+ DBG (DBG_ERR,
+ "process_request: bad status %d\n", w->status);
+ return -1;
+ }
+
+ current_request = word;
+
+ DBG (DBG_MSG, "process_request: got request %d\n", current_request);
+
+ switch (current_request)
+ {
+ case SANE_NET_GET_DEVICES:
+ {
+ SANE_Get_Devices_Reply reply;
+
+ reply.status =
+ sane_get_devices ((const SANE_Device ***) &reply.device_list,
+ SANE_TRUE);
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_get_devices_reply, &reply);
+ }
+ break;
+
+ case SANE_NET_OPEN:
+ {
+ SANE_Open_Reply reply;
+ SANE_Handle be_handle;
+ SANE_String name, resource;
+
+ sanei_w_string (w, &name);
+ if (w->status)
+ {
+ DBG (DBG_ERR,
+ "process_request: (open) error while decoding args (%s)\n",
+ strerror (w->status));
+ return 1;
+ }
+
+ if (!name)
+ {
+ DBG (DBG_ERR, "process_request: (open) device_name == NULL\n");
+ reply.status = SANE_STATUS_INVAL;
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
+ return 1;
+ }
+
+ can_authorize = 1;
+
+ resource = strdup (name);
+
+ if (strlen(resource) == 0) {
+
+ const SANE_Device **device_list;
+
+ DBG(DBG_DBG, "process_request: (open) strlen(resource) == 0\n");
+ free (resource);
+
+ if ((i = sane_get_devices (&device_list, SANE_TRUE)) !=
+ SANE_STATUS_GOOD)
+ {
+ DBG(DBG_ERR, "process_request: (open) sane_get_devices failed\n");
+ memset (&reply, 0, sizeof (reply));
+ reply.status = i;
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
+ break;
+ }
+
+ if ((device_list == NULL) || (device_list[0] == NULL))
+ {
+ DBG(DBG_ERR, "process_request: (open) device_list[0] == 0\n");
+ memset (&reply, 0, sizeof (reply));
+ reply.status = SANE_STATUS_INVAL;
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
+ break;
+ }
+
+ resource = strdup (device_list[0]->name);
+ }
+
+ if (strchr (resource, ':'))
+ *(strchr (resource, ':')) = 0;
+
+ if (sanei_authorize (resource, "saned", auth_callback) !=
+ SANE_STATUS_GOOD)
+ {
+ DBG (DBG_ERR, "process_request: access to resource `%s' denied\n",
+ resource);
+ free (resource);
+ memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
+ reply.status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else
+ {
+ DBG (DBG_MSG, "process_request: access to resource `%s' granted\n",
+ resource);
+ free (resource);
+ memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
+ reply.status = sane_open (name, &be_handle);
+ DBG (DBG_MSG, "process_request: sane_open returned: %s\n",
+ sane_strstatus (reply.status));
+ }
+
+ if (reply.status == SANE_STATUS_GOOD)
+ {
+ h = get_free_handle ();
+ if (h < 0)
+ reply.status = SANE_STATUS_NO_MEM;
+ else
+ {
+ handle[h].handle = be_handle;
+ reply.handle = h;
+ }
+ }
+
+ can_authorize = 0;
+
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
+ sanei_w_free (w, (WireCodecFunc) sanei_w_string, &name);
+ }
+ break;
+
+ case SANE_NET_CLOSE:
+ {
+ SANE_Word ack = 0;
+
+ h = decode_handle (w, "close");
+ close_handle (h);
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_word, &ack);
+ }
+ break;
+
+ case SANE_NET_GET_OPTION_DESCRIPTORS:
+ {
+ SANE_Option_Descriptor_Array opt;
+
+ h = decode_handle (w, "get_option_descriptors");
+ if (h < 0)
+ return 1;
+ be_handle = handle[h].handle;
+ sane_control_option (be_handle, 0, SANE_ACTION_GET_VALUE,
+ &opt.num_options, 0);
+
+ opt.desc = malloc (opt.num_options * sizeof (opt.desc[0]));
+ for (i = 0; i < opt.num_options; ++i)
+ opt.desc[i] = (SANE_Option_Descriptor *)
+ sane_get_option_descriptor (be_handle, i);
+
+ sanei_w_reply (w,(WireCodecFunc) sanei_w_option_descriptor_array,
+ &opt);
+
+ free (opt.desc);
+ }
+ break;
+
+ case SANE_NET_CONTROL_OPTION:
+ {
+ SANE_Control_Option_Req req;
+ SANE_Control_Option_Reply reply;
+
+ sanei_w_control_option_req (w, &req);
+ if (w->status || (unsigned) req.handle >= (unsigned) num_handles
+ || !handle[req.handle].inuse)
+ {
+ DBG (DBG_ERR,
+ "process_request: (control_option) "
+ "error while decoding args h=%d (%s)\n"
+ , req.handle, strerror (w->status));
+ return 1;
+ }
+
+ can_authorize = 1;
+
+ memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
+ be_handle = handle[req.handle].handle;
+ reply.status = sane_control_option (be_handle, req.option,
+ req.action, req.value,
+ &reply.info);
+ reply.value_type = req.value_type;
+ reply.value_size = req.value_size;
+ reply.value = req.value;
+
+ can_authorize = 0;
+
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_control_option_reply,
+ &reply);
+ sanei_w_free (w, (WireCodecFunc) sanei_w_control_option_req, &req);
+ }
+ break;
+
+ case SANE_NET_GET_PARAMETERS:
+ {
+ SANE_Get_Parameters_Reply reply;
+
+ h = decode_handle (w, "get_parameters");
+ if (h < 0)
+ return 1;
+ be_handle = handle[h].handle;
+
+ reply.status = sane_get_parameters (be_handle, &reply.params);
+
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_get_parameters_reply,
+ &reply);
+ }
+ break;
+
+ case SANE_NET_START:
+ {
+ SANE_Start_Reply reply;
+ int fd = -1, data_fd;
+
+ h = decode_handle (w, "start");
+ if (h < 0)
+ return 1;
+
+ memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
+ reply.byte_order = SANE_NET_LITTLE_ENDIAN;
+ if (byte_order.w != 1)
+ reply.byte_order = SANE_NET_BIG_ENDIAN;
+
+ if (handle[h].scanning)
+ reply.status = SANE_STATUS_DEVICE_BUSY;
+ else
+ fd = start_scan (w, h, &reply);
+
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_start_reply, &reply);
+
+#ifdef SANED_USES_AF_INDEP
+ if (reply.status == SANE_STATUS_GOOD)
+ {
+ struct sockaddr_storage ss;
+ char text_addr[64];
+ int len;
+ int error;
+
+ DBG (DBG_MSG, "process_request: waiting for data connection\n");
+ data_fd = accept (fd, 0, 0);
+ close (fd);
+
+ /* Get address of remote host */
+ len = sizeof (ss);
+ if (getpeername (data_fd, (struct sockaddr *) &ss, (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "process_request: getpeername failed: %s\n",
+ strerror (errno));
+ return 1;
+ }
+
+ error = getnameinfo ((struct sockaddr *) &ss, len, text_addr,
+ sizeof (text_addr), NULL, 0, NI_NUMERICHOST);
+ if (error)
+ {
+ DBG (DBG_ERR, "process_request: getnameinfo failed: %s\n",
+ gai_strerror (error));
+ return 1;
+ }
+
+ DBG (DBG_MSG, "process_request: access to data port from %s\n",
+ text_addr);
+
+ if (strcmp (text_addr, remote_ip) != 0)
+ {
+ DBG (DBG_ERR, "process_request: however, only %s is authorized\n",
+ text_addr);
+ DBG (DBG_ERR, "process_request: configuration problem or attack?\n");
+ close (data_fd);
+ data_fd = -1;
+ return -1;
+ }
+
+#else /* !SANED_USES_AF_INDEP */
+
+ if (reply.status == SANE_STATUS_GOOD)
+ {
+ struct sockaddr_in sin;
+ int len;
+
+ DBG (DBG_MSG, "process_request: waiting for data connection\n");
+ data_fd = accept (fd, 0, 0);
+ close (fd);
+
+ /* Get address of remote host */
+ len = sizeof (sin);
+ if (getpeername (data_fd, (struct sockaddr *) &sin,
+ (socklen_t *) &len) < 0)
+ {
+ DBG (DBG_ERR, "process_request: getpeername failed: %s\n",
+ strerror (errno));
+ return 1;
+ }
+
+ if (memcmp (&remote_address, &sin.sin_addr,
+ sizeof (remote_address)) != 0)
+ {
+ DBG (DBG_ERR,
+ "process_request: access to data port from %s\n",
+ inet_ntoa (sin.sin_addr));
+ DBG (DBG_ERR,
+ "process_request: however, only %s is authorized\n",
+ inet_ntoa (remote_address));
+ DBG (DBG_ERR,
+ "process_request: configuration problem or attack?\n");
+ close (data_fd);
+ data_fd = -1;
+ return -1;
+ }
+ else
+ DBG (DBG_MSG, "process_request: access to data port from %s\n",
+ inet_ntoa (sin.sin_addr));
+#endif /* SANED_USES_AF_INDEP */
+
+ if (data_fd < 0)
+ {
+ sane_cancel (handle[h].handle);
+ handle[h].scanning = 0;
+ handle[h].docancel = 0;
+ DBG (DBG_ERR, "process_request: accept failed! (%s)\n",
+ strerror (errno));
+ return 1;
+ }
+ fcntl (data_fd, F_SETFL, 1); /* set non-blocking */
+ shutdown (data_fd, 0);
+ do_scan (w, h, data_fd);
+ close (data_fd);
+ }
+ }
+ break;
+
+ case SANE_NET_CANCEL:
+ {
+ SANE_Word ack = 0;
+
+ h = decode_handle (w, "cancel");
+ if (h >= 0)
+ {
+ sane_cancel (handle[h].handle);
+ handle[h].docancel = 1;
+ }
+ sanei_w_reply (w, (WireCodecFunc) sanei_w_word, &ack);
+ }
+ break;
+
+ case SANE_NET_EXIT:
+ return -1;
+ break;
+
+ case SANE_NET_INIT:
+ case SANE_NET_AUTHORIZE:
+ default:
+ DBG (DBG_ERR,
+ "process_request: received unexpected procedure number %d\n",
+ current_request);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+wait_child (pid_t pid, int *status, int options)
+{
+ struct saned_child *c;
+ struct saned_child *p = NULL;
+ int ret;
+
+ ret = waitpid(pid, status, options);
+
+ if (ret <= 0)
+ return ret;
+
+#ifdef WITH_AVAHI
+ if ((avahi_pid > 0) && (ret == avahi_pid))
+ {
+ avahi_pid = -1;
+ numchildren--;
+ return ret;
+ }
+#endif /* WITH_AVAHI */
+
+ for (c = children; (c != NULL) && (c->next != NULL); p = c, c = c->next)
+ {
+ if (c->pid == ret)
+ {
+ if (c == children)
+ children = c->next;
+ else if (p != NULL)
+ p->next = c->next;
+
+ free(c);
+
+ numchildren--;
+
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int
+add_child (pid_t pid)
+{
+ struct saned_child *c;
+
+ c = (struct saned_child *) malloc (sizeof(struct saned_child));
+
+ if (c == NULL)
+ {
+ DBG (DBG_ERR, "add_child: out of memory\n");
+ return -1;
+ }
+
+ c->pid = pid;
+ c->next = children;
+
+ children = c;
+
+ return 0;
+}
+
+
+static void
+handle_connection (int fd)
+{
+#ifdef TCP_NODELAY
+ int on = 1;
+ int level = -1;
+#endif
+
+ DBG (DBG_DBG, "handle_connection: processing client connection\n");
+
+ wire.io.fd = fd;
+
+ signal (SIGALRM, quit);
+ signal (SIGPIPE, quit);
+
+#ifdef TCP_NODELAY
+# ifdef SOL_TCP
+ level = SOL_TCP;
+# else /* !SOL_TCP */
+ /* Look up the protocol level in the protocols database. */
+ {
+ struct protoent *p;
+ p = getprotobyname ("tcp");
+ if (p == 0)
+ {
+ DBG (DBG_WARN, "handle_connection: cannot look up `tcp' protocol number");
+ }
+ else
+ level = p->p_proto;
+ }
+# endif /* SOL_TCP */
+ if (level == -1
+ || setsockopt (wire.io.fd, level, TCP_NODELAY, &on, sizeof (on)))
+ DBG (DBG_WARN, "handle_connection: failed to put socket in TCP_NODELAY mode (%s)",
+ strerror (errno));
+#endif /* !TCP_NODELAY */
+
+ if (init (&wire) < 0)
+ return;
+
+ while (1)
+ {
+ reset_watchdog ();
+ if (process_request (&wire) < 0)
+ break;
+ }
+}
+
+static void
+handle_client (int fd)
+{
+ pid_t pid;
+ int i;
+
+ DBG (DBG_DBG, "handle_client: spawning child process\n");
+
+ pid = fork ();
+ if (pid == 0)
+ {
+ /* child */
+ if (log_to_syslog)
+ closelog();
+
+ for (i = 3; i < fd; i++)
+ close(i);
+
+ if (log_to_syslog)
+ openlog ("saned", LOG_PID | LOG_CONS, LOG_DAEMON);
+
+ handle_connection (fd);
+ quit (0);
+ }
+ else if (pid > 0)
+ {
+ /* parent */
+ add_child (pid);
+ close(fd);
+ }
+ else
+ {
+ /* FAILED */
+ DBG (DBG_ERR, "handle_client: fork() failed: %s\n", strerror (errno));
+ close(fd);
+ }
+}
+
+static void
+bail_out (int error)
+{
+ DBG (DBG_ERR, "%sbailing out, waiting for children...\n", (error) ? "FATAL ERROR; " : "");
+
+#ifdef WITH_AVAHI
+ if (avahi_pid > 0)
+ kill (avahi_pid, SIGTERM);
+#endif /* WITH_AVAHI */
+
+ while (numchildren > 0)
+ wait_child (-1, NULL, 0);
+
+ DBG (DBG_ERR, "bail_out: all children exited\n");
+
+ exit ((error) ? 1 : 0);
+}
+
+void
+sig_int_term_handler (int signum);
+
+void
+sig_int_term_handler (int signum)
+{
+ /* unused */
+ signum = signum;
+
+ signal (SIGINT, NULL);
+ signal (SIGTERM, NULL);
+
+ bail_out (0);
+}
+
+
+#ifdef WITH_AVAHI
+static void
+saned_avahi (struct pollfd *fds, int nfds);
+
+static void
+saned_create_avahi_services (AvahiClient *c);
+
+static void
+saned_avahi_callback (AvahiClient *c, AvahiClientState state, void *userdata);
+
+static void
+saned_avahi_group_callback (AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata);
+
+
+static void
+saned_avahi (struct pollfd *fds, int nfds)
+{
+ struct pollfd *fdp = NULL;
+ int error;
+
+ avahi_pid = fork ();
+
+ if (avahi_pid > 0)
+ {
+ numchildren++;
+ return;
+ }
+ else if (avahi_pid < 0)
+ {
+ DBG (DBG_ERR, "saned_avahi: could not spawn Avahi process: %s\n", strerror (errno));
+ return;
+ }
+
+ signal (SIGINT, NULL);
+ signal (SIGTERM, NULL);
+
+ /* Close network fds */
+ for (fdp = fds; nfds > 0; nfds--, fdp++)
+ close (fdp->fd);
+
+ free(fds);
+
+ avahi_svc_name = avahi_strdup(SANED_NAME);
+
+ avahi_poll = avahi_simple_poll_new ();
+ if (avahi_poll == NULL)
+ {
+ DBG (DBG_ERR, "saned_avahi: failed to create simple poll object\n");
+ goto fail;
+ }
+
+ avahi_client = avahi_client_new (avahi_simple_poll_get (avahi_poll), AVAHI_CLIENT_NO_FAIL, saned_avahi_callback, NULL, &error);
+ if (avahi_client == NULL)
+ {
+ DBG (DBG_ERR, "saned_avahi: failed to create client: %s\n", avahi_strerror (error));
+ goto fail;
+ }
+
+ avahi_simple_poll_loop (avahi_poll);
+
+ DBG (DBG_INFO, "saned_avahi: poll loop exited\n");
+
+ exit(EXIT_SUCCESS);
+
+ /* NOT REACHED */
+ return;
+
+ fail:
+ if (avahi_client)
+ avahi_client_free (avahi_client);
+
+ if (avahi_poll)
+ avahi_simple_poll_free (avahi_poll);
+
+ avahi_free (avahi_svc_name);
+
+ exit(EXIT_FAILURE);
+}
+
+static void
+saned_avahi_group_callback (AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)
+{
+ char *n;
+
+ /* unused */
+ userdata = userdata;
+
+ if ((!g) || (g != avahi_group))
+ return;
+
+ switch (state)
+ {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ /* The entry group has been established successfully */
+ DBG (DBG_INFO, "saned_avahi_group_callback: service '%s' successfully established\n", avahi_svc_name);
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ /* A service name collision with a remote service
+ * happened. Let's pick a new name */
+ n = avahi_alternative_service_name (avahi_svc_name);
+ avahi_free (avahi_svc_name);
+ avahi_svc_name = n;
+
+ DBG (DBG_WARN, "saned_avahi_group_callback: service name collision, renaming service to '%s'\n", avahi_svc_name);
+
+ /* And recreate the services */
+ saned_create_avahi_services (avahi_entry_group_get_client (g));
+ break;
+
+ case AVAHI_ENTRY_GROUP_FAILURE :
+ DBG (DBG_ERR, "saned_avahi_group_callback: entry group failure: %s\n", avahi_strerror (avahi_client_errno (avahi_entry_group_get_client (g))));
+
+ /* Some kind of failure happened while we were registering our services */
+ avahi_simple_poll_quit (avahi_poll);
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ break;
+ }
+}
+
+static void
+saned_create_avahi_services (AvahiClient *c)
+{
+ char *n;
+ char txt[32];
+ AvahiProtocol proto;
+ int ret;
+
+ if (!c)
+ return;
+
+ if (!avahi_group)
+ {
+ avahi_group = avahi_entry_group_new (c, saned_avahi_group_callback, NULL);
+ if (avahi_group == NULL)
+ {
+ DBG (DBG_ERR, "saned_create_avahi_services: avahi_entry_group_new() failed: %s\n", avahi_strerror (avahi_client_errno (c)));
+ goto fail;
+ }
+ }
+
+ if (avahi_entry_group_is_empty (avahi_group))
+ {
+ DBG (DBG_INFO, "saned_create_avahi_services: adding service '%s'\n", avahi_svc_name);
+
+ snprintf(txt, sizeof (txt), "protovers=%x", SANE_VERSION_CODE (V_MAJOR, V_MINOR, SANEI_NET_PROTOCOL_VERSION));
+
+#ifdef ENABLE_IPV6
+ proto = AVAHI_PROTO_UNSPEC;
+#else
+ proto = AVAHI_PROTO_INET;
+#endif /* ENABLE_IPV6 */
+
+ ret = avahi_entry_group_add_service (avahi_group, AVAHI_IF_UNSPEC, proto, 0, avahi_svc_name, SANED_SERVICE_DNS, NULL, NULL, SANED_SERVICE_PORT, txt, NULL);
+ if (ret < 0)
+ {
+ if (ret == AVAHI_ERR_COLLISION)
+ {
+ n = avahi_alternative_service_name (avahi_svc_name);
+ avahi_free (avahi_svc_name);
+ avahi_svc_name = n;
+
+ DBG (DBG_WARN, "saned_create_avahi_services: service name collision, renaming service to '%s'\n", avahi_svc_name);
+
+ avahi_entry_group_reset (avahi_group);
+
+ saned_create_avahi_services (c);
+
+ return;
+ }
+
+ DBG (DBG_ERR, "saned_create_avahi_services: failed to add %s service: %s\n", SANED_SERVICE_DNS, avahi_strerror (ret));
+ goto fail;
+ }
+
+ /* Tell the server to register the service */
+ ret = avahi_entry_group_commit (avahi_group);
+ if (ret < 0)
+ {
+ DBG (DBG_ERR, "saned_create_avahi_services: failed to commit entry group: %s\n", avahi_strerror (ret));
+ goto fail;
+ }
+ }
+
+ return;
+
+ fail:
+ avahi_simple_poll_quit (avahi_poll);
+}
+
+static void
+saned_avahi_callback (AvahiClient *c, AvahiClientState state, void *userdata)
+{
+ int error;
+
+ /* unused */
+ userdata = userdata;
+
+ if (!c)
+ return;
+
+ switch (state)
+ {
+ case AVAHI_CLIENT_CONNECTING:
+ DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_CONNECTING\n");
+ break;
+
+ case AVAHI_CLIENT_S_RUNNING:
+ DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_S_RUNNING\n");
+ saned_create_avahi_services (c);
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_S_COLLISION\n");
+
+ case AVAHI_CLIENT_S_REGISTERING:
+ DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_S_REGISTERING\n");
+ if (avahi_group)
+ avahi_entry_group_reset (avahi_group);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_FAILURE\n");
+
+ error = avahi_client_errno (c);
+ if (error == AVAHI_ERR_DISCONNECTED)
+ {
+ DBG (DBG_INFO, "saned_avahi_callback: AVAHI_ERR_DISCONNECTED\n");
+
+ /* Server disappeared - try to reconnect */
+ avahi_client_free (avahi_client);
+ avahi_client = NULL;
+ avahi_group = NULL;
+
+ avahi_client = avahi_client_new (avahi_simple_poll_get (avahi_poll), AVAHI_CLIENT_NO_FAIL, saned_avahi_callback, NULL, &error);
+ if (avahi_client == NULL)
+ {
+ DBG (DBG_ERR, "saned_avahi_callback: failed to create client: %s\n", avahi_strerror (error));
+ avahi_simple_poll_quit (avahi_poll);
+ }
+ }
+ else
+ {
+ /* Another error happened - game over */
+ DBG (DBG_ERR, "saned_avahi_callback: client failure: %s\n", avahi_strerror (error));
+ avahi_simple_poll_quit (avahi_poll);
+ }
+ break;
+ }
+}
+#endif /* WITH_AVAHI */
+
+
+static void
+read_config (void)
+{
+ char config_line[PATH_MAX];
+ const char *optval;
+ char *endval;
+ long val;
+ FILE *fp;
+ int len;
+
+ DBG (DBG_INFO, "read_config: searching for config file\n");
+ fp = sanei_config_open (SANED_CONFIG_FILE);
+ if (fp)
+ {
+ while (sanei_config_read (config_line, sizeof (config_line), fp))
+ {
+ if (config_line[0] == '#')
+ continue; /* ignore line comments */
+
+ optval = strchr (config_line, '=');
+ if (optval == NULL)
+ continue; /* only interested in options, skip hosts */
+
+ len = strlen (config_line);
+ if (!len)
+ continue; /* ignore empty lines */
+
+ /*
+ * Check for saned options.
+ * Anything that isn't an option is a client.
+ */
+ if (strstr(config_line, "data_portrange") != NULL)
+ {
+ optval = sanei_config_skip_whitespace (++optval);
+ if ((optval != NULL) && (*optval != '\0'))
+ {
+ val = strtol (optval, &endval, 10);
+ if (optval == endval)
+ {
+ DBG (DBG_ERR, "read_config: invalid value for data_portrange\n");
+ continue;
+ }
+ else if ((val < 0) || (val > 65535))
+ {
+ DBG (DBG_ERR, "read_config: data_portrange start port is invalid\n");
+ continue;
+ }
+
+ optval = strchr (endval, '-');
+ if (optval == NULL)
+ {
+ DBG (DBG_ERR, "read_config: no end port value for data_portrange\n");
+ continue;
+ }
+
+ optval = sanei_config_skip_whitespace (++optval);
+
+ data_port_lo = val;
+
+ val = strtol (optval, &endval, 10);
+ if (optval == endval)
+ {
+ DBG (DBG_ERR, "read_config: invalid value for data_portrange\n");
+ data_port_lo = 0;
+ continue;
+ }
+ else if ((val < 0) || (val > 65535))
+ {
+ DBG (DBG_ERR, "read_config: data_portrange end port is invalid\n");
+ data_port_lo = 0;
+ continue;
+ }
+ else if (val < data_port_lo)
+ {
+ DBG (DBG_ERR, "read_config: data_portrange end port is less than start port\n");
+ data_port_lo = 0;
+ continue;
+ }
+
+ data_port_hi = val;
+
+ DBG (DBG_INFO, "read_config: data port range: %d - %d\n", data_port_lo, data_port_hi);
+ }
+ }
+ }
+ fclose (fp);
+ DBG (DBG_INFO, "read_config: done reading config\n");
+ }
+ else
+ DBG (DBG_ERR, "read_config: could not open config file (%s): %s\n",
+ SANED_CONFIG_FILE, strerror (errno));
+}
+
+
+#ifdef SANED_USES_AF_INDEP
+static void
+do_bindings_family (int family, int *nfds, struct pollfd **fds, struct addrinfo *res)
+{
+ struct addrinfo *resp;
+ struct pollfd *fdp;
+ short sane_port;
+ int fd = -1;
+ int on = 1;
+ int i;
+
+ fdp = *fds;
+
+ for (resp = res, i = 0; resp != NULL; resp = resp->ai_next, i++)
+ {
+ /* We're not interested */
+ if (resp->ai_family != family)
+ continue;
+
+ if (resp->ai_family == AF_INET)
+ {
+ sane_port = ntohs (((struct sockaddr_in *) resp->ai_addr)->sin_port);
+ }
+#ifdef ENABLE_IPV6
+ else if (resp->ai_family == AF_INET6)
+ {
+ sane_port = ntohs (((struct sockaddr_in6 *) resp->ai_addr)->sin6_port);
+ }
+#endif /* ENABLE_IPV6 */
+ else
+ continue;
+
+ DBG (DBG_DBG, "do_bindings: [%d] socket () using IPv%d\n", i, (family == AF_INET) ? 4 : 6);
+ if ((fd = socket (resp->ai_family, SOCK_STREAM, 0)) < 0)
+ {
+ DBG (DBG_ERR, "do_bindings: [%d] socket failed: %s\n", i, strerror (errno));
+
+ continue;
+ }
+
+ DBG (DBG_DBG, "do_bindings: [%d] setsockopt ()\n", i);
+ if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)))
+ DBG (DBG_ERR, "do_bindings: [%d] failed to put socket in SO_REUSEADDR mode (%s)\n", i, strerror (errno));
+
+
+ DBG (DBG_DBG, "do_bindings: [%d] bind () to port %d\n", i, sane_port);
+ if (bind (fd, resp->ai_addr, resp->ai_addrlen) < 0)
+ {
+ /*
+ * Binding a socket may fail with EADDRINUSE if we already bound
+ * to an IPv6 addr returned by getaddrinfo (usually the first ones)
+ * and we're trying to bind to an IPv4 addr now.
+ * It can also fail because we're trying to bind an IPv6 socket and IPv6
+ * is not functional on this machine.
+ * In any case, a bind() call returning an error is not necessarily fatal.
+ */
+ DBG (DBG_WARN, "do_bindings: [%d] bind failed: %s\n", i, strerror (errno));
+
+ close (fd);
+
+ continue;
+ }
+
+ DBG (DBG_DBG, "do_bindings: [%d] listen ()\n", i);
+ if (listen (fd, 1) < 0)
+ {
+ DBG (DBG_ERR, "do_bindings: [%d] listen failed: %s\n", i, strerror (errno));
+
+ close (fd);
+
+ continue;
+ }
+
+ fdp->fd = fd;
+ fdp->events = POLLIN;
+
+ (*nfds)++;
+ fdp++;
+ }
+
+ *fds = fdp;
+}
+
+static void
+do_bindings (int *nfds, struct pollfd **fds)
+{
+ struct addrinfo *res;
+ struct addrinfo *resp;
+ struct addrinfo hints;
+ struct pollfd *fdp;
+ int err;
+
+ DBG (DBG_DBG, "do_bindings: trying to get port for service \"%s\" (getaddrinfo)\n", SANED_SERVICE_NAME);
+
+ memset (&hints, 0, sizeof (struct addrinfo));
+
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+
+ err = getaddrinfo (NULL, SANED_SERVICE_NAME, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_WARN, "do_bindings: \" %s \" service unknown on your host; you should add\n", SANED_SERVICE_NAME);
+ DBG (DBG_WARN, "do_bindings: %s %d/tcp saned # SANE network scanner daemon\n", SANED_SERVICE_NAME, SANED_SERVICE_PORT);
+ DBG (DBG_WARN, "do_bindings: to your /etc/services file (or equivalent). Proceeding anyway.\n");
+ err = getaddrinfo (NULL, SANED_SERVICE_PORT_S, &hints, &res);
+ if (err)
+ {
+ DBG (DBG_ERR, "do_bindings: getaddrinfo() failed even with numeric port: %s\n", gai_strerror (err));
+ bail_out (1);
+ }
+ }
+
+ for (resp = res, *nfds = 0; resp != NULL; resp = resp->ai_next, (*nfds)++)
+ ;
+
+ *fds = malloc (*nfds * sizeof (struct pollfd));
+
+ if (fds == NULL)
+ {
+ DBG (DBG_ERR, "do_bindings: not enough memory for fds\n");
+ freeaddrinfo (res);
+ bail_out (1);
+ }
+
+ fdp = *fds;
+ *nfds = 0;
+
+ /* bind IPv6 first, IPv4 second */
+#ifdef ENABLE_IPV6
+ do_bindings_family (AF_INET6, nfds, &fdp, res);
+#endif
+ do_bindings_family (AF_INET, nfds, &fdp, res);
+
+ resp = NULL;
+ freeaddrinfo (res);
+
+ if (*nfds <= 0)
+ {
+ DBG (DBG_ERR, "do_bindings: couldn't bind an address. Exiting.\n");
+ bail_out (1);
+ }
+}
+
+#else /* !SANED_USES_AF_INDEP */
+
+static void
+do_bindings (int *nfds, struct pollfd **fds)
+{
+ struct sockaddr_in sin;
+ struct servent *serv;
+ short port;
+ int fd = -1;
+ int on = 1;
+
+ DBG (DBG_DBG, "do_bindings: trying to get port for service \"%s\" (getservbyname)\n", SANED_SERVICE_NAME);
+ serv = getservbyname (SANED_SERVICE_NAME, "tcp");
+
+ if (serv)
+ {
+ port = serv->s_port;
+ DBG (DBG_MSG, "do_bindings: port is %d\n", ntohs (port));
+ }
+ else
+ {
+ port = htons (SANED_SERVICE_PORT);
+ DBG (DBG_WARN, "do_bindings: \"%s\" service unknown on your host; you should add\n", SANED_SERVICE_NAME);
+ DBG (DBG_WARN, "do_bindings: %s %d/tcp saned # SANE network scanner daemon\n", SANED_SERVICE_NAME, SANED_SERVICE_PORT);
+ DBG (DBG_WARN, "do_bindings: to your /etc/services file (or equivalent). Proceeding anyway.\n");
+ }
+
+ *nfds = 1;
+ *fds = malloc (*nfds * sizeof (struct pollfd));
+
+ if (fds == NULL)
+ {
+ DBG (DBG_ERR, "do_bindings: not enough memory for fds\n");
+ bail_out (1);
+ }
+
+ memset (&sin, 0, sizeof (sin));
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = port;
+
+ DBG (DBG_DBG, "do_bindings: socket ()\n");
+ fd = socket (AF_INET, SOCK_STREAM, 0);
+
+ DBG (DBG_DBG, "do_bindings: setsockopt ()\n");
+ if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)))
+ DBG (DBG_ERR, "do_bindings: failed to put socket in SO_REUSEADDR mode (%s)", strerror (errno));
+
+ DBG (DBG_DBG, "do_bindings: bind ()\n");
+ if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0)
+ {
+ DBG (DBG_ERR, "do_bindings: bind failed: %s", strerror (errno));
+ bail_out (1);
+ }
+
+ DBG (DBG_DBG, "do_bindings: listen ()\n");
+ if (listen (fd, 1) < 0)
+ {
+ DBG (DBG_ERR, "do_bindings: listen failed: %s", strerror (errno));
+ bail_out (1);
+ }
+
+ (*fds)->fd = fd;
+ (*fds)->events = POLLIN;
+}
+
+#endif /* SANED_USES_AF_INDEP */
+
+
+static void
+run_standalone (int argc, char **argv)
+{
+ struct pollfd *fds = NULL;
+ struct pollfd *fdp = NULL;
+ int nfds;
+ int fd = -1;
+ int i;
+ int ret;
+
+ uid_t runas_uid = 0;
+ gid_t runas_gid = 0;
+ struct passwd *pwent;
+ gid_t *grplist = NULL;
+ struct group *grp;
+ int ngroups = 0;
+ FILE *pidfile;
+
+ do_bindings (&nfds, &fds);
+
+ if (run_mode != SANED_RUN_DEBUG)
+ {
+ if (argc > 2)
+ {
+ pwent = getpwnam(argv[2]);
+
+ if (pwent == NULL)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: user %s not found on system\n", argv[2]);
+ bail_out (1);
+ }
+
+ runas_uid = pwent->pw_uid;
+ runas_gid = pwent->pw_gid;
+
+ /* Get group list for runas_uid */
+ grplist = (gid_t *)malloc(sizeof(gid_t));
+
+ if (grplist == NULL)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: cannot allocate memory for group list\n");
+
+ exit (1);
+ }
+
+ ngroups = 1;
+ grplist[0] = runas_gid;
+
+ setgrent();
+ while ((grp = getgrent()) != NULL)
+ {
+ int i = 0;
+
+ /* Already added current group */
+ if (grp->gr_gid == runas_gid)
+ continue;
+
+ while (grp->gr_mem[i])
+ {
+ if (strcmp(grp->gr_mem[i], argv[2]) == 0)
+ {
+ int need_to_add = 1, j;
+
+ /* Make sure its not already in list */
+ for (j = 0; j < ngroups; j++)
+ {
+ if (grp->gr_gid == grplist[i])
+ need_to_add = 0;
+ }
+ if (need_to_add)
+ {
+ grplist = (gid_t *)realloc(grplist,
+ sizeof(gid_t)*ngroups+1);
+ if (grplist == NULL)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: cannot reallocate memory for group list\n");
+
+ exit (1);
+ }
+ grplist[ngroups++] = grp->gr_gid;
+ }
+ }
+ i++;
+ }
+ }
+ endgrent();
+ }
+
+ DBG (DBG_MSG, "run_standalone: daemonizing now\n");
+
+ fd = open ("/dev/null", O_RDWR);
+ if (fd < 0)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: cannot open /dev/null: %s\n", strerror (errno));
+ exit (1);
+ }
+
+ ret = fork ();
+ if (ret > 0)
+ {
+ _exit (0);
+ }
+ else if (ret < 0)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: fork failed: %s\n", strerror (errno));
+ exit (1);
+ }
+
+ DBG (DBG_WARN, "Now daemonized\n");
+
+ /* Write out PID file */
+ pidfile = fopen (SANED_PID_FILE, "w");
+ if (pidfile)
+ {
+ fprintf (pidfile, "%d", getpid());
+ fclose (pidfile);
+ }
+ else
+ DBG (DBG_ERR, "Could not write PID file: %s\n", strerror (errno));
+
+ chdir ("/");
+
+ dup2 (fd, STDIN_FILENO);
+ dup2 (fd, STDOUT_FILENO);
+ dup2 (fd, STDERR_FILENO);
+
+ close (fd);
+
+ setsid ();
+
+ /* Drop privileges if requested */
+ if (runas_uid > 0)
+ {
+ ret = setgroups(ngroups, grplist);
+ if (ret < 0)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: could not set group list: %s\n", strerror(errno));
+
+ exit (1);
+ }
+
+ free(grplist);
+
+ ret = setegid (runas_gid);
+ if (ret < 0)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: setegid to gid %d failed: %s\n", runas_gid, strerror (errno));
+
+ exit (1);
+ }
+
+ ret = seteuid (runas_uid);
+ if (ret < 0)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: seteuid to uid %d failed: %s\n", runas_uid, strerror (errno));
+
+ exit (1);
+ }
+
+ DBG (DBG_WARN, "Dropped privileges to uid %d gid %d\n", runas_uid, runas_gid);
+ }
+
+ signal(SIGINT, sig_int_term_handler);
+ signal(SIGTERM, sig_int_term_handler);
+ }
+
+#ifdef WITH_AVAHI
+ DBG (DBG_INFO, "run_standalone: spawning Avahi process\n");
+ saned_avahi (fds, nfds);
+
+ /* NOT REACHED (Avahi process) */
+#endif /* WITH_AVAHI */
+
+ DBG (DBG_MSG, "run_standalone: waiting for control connection\n");
+
+ while (1)
+ {
+ ret = poll (fds, nfds, 500);
+ if (ret < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ else
+ {
+ DBG (DBG_ERR, "run_standalone: poll failed: %s\n", strerror (errno));
+ free (fds);
+ bail_out (1);
+ }
+ }
+
+ /* Wait for children */
+ while (wait_child (-1, NULL, WNOHANG) > 0)
+ ;
+
+ if (ret == 0)
+ continue;
+
+ for (i = 0, fdp = fds; i < nfds; i++, fdp++)
+ {
+ /* Error on an fd */
+ if (fdp->revents & (POLLERR | POLLHUP | POLLNVAL))
+ {
+ for (i = 0, fdp = fds; i < nfds; i++, fdp++)
+ close (fdp->fd);
+
+ free (fds);
+
+ DBG (DBG_WARN, "run_standalone: invalid fd in set, attempting to re-bind\n");
+
+ /* Reopen sockets */
+ do_bindings (&nfds, &fds);
+
+ break;
+ }
+ else if (! (fdp->revents & POLLIN))
+ continue;
+
+ fd = accept (fdp->fd, 0, 0);
+ if (fd < 0)
+ {
+ DBG (DBG_ERR, "run_standalone: accept failed: %s", strerror (errno));
+ continue;
+ }
+
+ if (run_mode == SANED_RUN_DEBUG)
+ break; /* We have the only connection we're going to handle */
+ else
+ handle_client (fd);
+ }
+
+ if (run_mode == SANED_RUN_DEBUG)
+ break;
+ }
+
+ for (i = 0, fdp = fds; i < nfds; i++, fdp++)
+ close (fdp->fd);
+
+ free (fds);
+
+ if (run_mode == SANED_RUN_DEBUG)
+ {
+ if (fd > 0)
+ handle_connection (fd);
+
+ bail_out(0);
+ }
+}
+
+
+static void
+run_inetd (int argc, char **argv)
+{
+
+ int fd = -1;
+
+#ifdef HAVE_SYSTEMD
+ int n;
+
+ n = sd_listen_fds(0);
+
+ if (n > 1)
+ {
+ DBG (DBG_ERR, "run_inetd: Too many file descriptors (sockets) received from systemd!\n");
+ return;
+ }
+
+ if (n == 1)
+ {
+ fd = SD_LISTEN_FDS_START + 0;
+ DBG (DBG_INFO, "run_inetd: Using systemd socket %d!\n", fd);
+ }
+#endif
+
+ if (fd == -1)
+ {
+ int dave_null;
+
+ /* Some backends really can't keep their dirty fingers off
+ * stdin/stdout/stderr; we work around them here so they don't
+ * mess up the network dialog and crash the remote net backend
+ * by messing with the inetd socket.
+ * For systemd this not an issue as systemd uses fd >= 3 for the
+ * socket and can even redirect stdout and stderr to syslog.
+ * We can even use this to get the debug logging
+ */
+ do
+ {
+ /* get new fd for the inetd socket */
+ fd = dup (1);
+
+ if (fd == -1)
+ {
+ DBG (DBG_ERR, "run_inetd: duplicating fd failed: %s", strerror (errno));
+ return;
+ }
+ }
+ while (fd < 3);
+
+ /* Our good'ole friend Dave Null to the rescue */
+ dave_null = open ("/dev/null", O_RDWR);
+ if (dave_null < 0)
+ {
+ DBG (DBG_ERR, "run_inetd: could not open /dev/null: %s", strerror (errno));
+ return;
+ }
+
+ close (STDIN_FILENO);
+ close (STDOUT_FILENO);
+ close (STDERR_FILENO);
+
+ dup2 (dave_null, STDIN_FILENO);
+ dup2 (dave_null, STDOUT_FILENO);
+ dup2 (dave_null, STDERR_FILENO);
+
+ close (dave_null);
+ }
+#ifndef HAVE_OS2_H
+ /* Unused in this function */
+ argc = argc;
+ argv = argv;
+
+#else
+ /* under OS/2, the socket handle is passed as argument on the command
+ line; the socket handle is relative to IBM TCP/IP, so a call
+ to impsockethandle() is required to add it to the EMX runtime */
+ if (argc == 2)
+ {
+ fd = _impsockhandle (atoi (argv[1]), 0);
+ if (fd == -1)
+ perror ("impsockhandle");
+ }
+#endif /* HAVE_OS2_H */
+
+ handle_connection(fd);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ char options[64] = "";
+ debug = DBG_WARN;
+
+ prog_name = strrchr (argv[0], '/');
+ if (prog_name)
+ ++prog_name;
+ else
+ prog_name = argv[0];
+
+ numchildren = 0;
+ run_mode = SANED_RUN_INETD;
+
+ if (argc >= 2)
+ {
+ if (strncmp (argv[1], "-a", 2) == 0)
+ run_mode = SANED_RUN_ALONE;
+ else if (strncmp (argv[1], "-d", 2) == 0)
+ {
+ run_mode = SANED_RUN_DEBUG;
+ log_to_syslog = SANE_FALSE;
+ }
+ else if (strncmp (argv[1], "-s", 2) == 0)
+ run_mode = SANED_RUN_DEBUG;
+ else
+ {
+ printf ("Usage: saned [ -a [ username ] | -d [ n ] | -s [ n ] ] | -h\n");
+ if ((strncmp (argv[1], "-h", 2) == 0) ||
+ (strncmp (argv[1], "--help", 6) == 0))
+ exit (EXIT_SUCCESS);
+ else
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (run_mode == SANED_RUN_DEBUG)
+ {
+ if (argv[1][2])
+ debug = atoi (argv[1] + 2);
+
+ DBG (DBG_WARN, "main: starting debug mode (level %d)\n", debug);
+ }
+
+ if (log_to_syslog)
+ openlog ("saned", LOG_PID | LOG_CONS, LOG_DAEMON);
+
+ read_config ();
+
+ byte_order.w = 0;
+ byte_order.ch = 1;
+
+ sanei_w_init (&wire, sanei_codec_bin_init);
+ wire.io.read = read;
+ wire.io.write = write;
+
+#ifdef SANED_USES_AF_INDEP
+ strcat(options, "AF-indep");
+# ifdef ENABLE_IPV6
+ strcat(options, "+IPv6");
+#endif
+#else
+ strcat(options, "IPv4 only");
+#endif
+#ifdef HAVE_SYSTEMD
+ if (sd_listen_fds(0) > 0)
+ {
+ strcat(options, "+systemd");
+ }
+#endif
+
+ if (strlen(options) > 0)
+ {
+ DBG (DBG_WARN, "saned (%s) from %s starting up\n", options, PACKAGE_STRING);
+ }
+ else
+ {
+ DBG (DBG_WARN, "saned from %s ready\n", PACKAGE_STRING);
+ }
+
+ if ((run_mode == SANED_RUN_ALONE) || (run_mode == SANED_RUN_DEBUG))
+ {
+ run_standalone(argc, argv);
+ }
+ else
+ {
+ run_inetd(argc, argv);
+ }
+
+ DBG (DBG_WARN, "saned exiting\n");
+
+ return 0;
+}
diff --git a/frontend/scanimage.c b/frontend/scanimage.c
new file mode 100644
index 0000000..d41c849
--- /dev/null
+++ b/frontend/scanimage.c
@@ -0,0 +1,2365 @@
+/* scanimage -- command line scanning utility
+ Uses the SANE library.
+ Copyright (C) 1996, 1997, 1998 Andreas Beck and David Mosberger
+
+ Copyright (C) 1999 - 2009 by the SANE Project -- See AUTHORS and ChangeLog
+ for details.
+
+ For questions and comments contact the sane-devel mailinglist (see
+ http://www.sane-project.org/mailing-lists.html).
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef _AIX
+# include "../include/lalloca.h" /* MUST come first for AIX! */
+#endif
+
+#include "../include/sane/config.h"
+#include "../include/lalloca.h"
+
+#include <assert.h>
+#include "lgetopt.h"
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "../include/_stdint.h"
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/saneopts.h"
+
+#include "stiff.h"
+
+#include "../include/md5.h"
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+
+#ifndef HAVE_ATEXIT
+# define atexit(func) on_exit(func, 0) /* works for SunOS, at least */
+#endif
+
+typedef struct
+{
+ uint8_t *data;
+ int width; /*WARNING: this is in bytes, get pixel width from param*/
+ int height;
+ int x;
+ int y;
+}
+Image;
+
+#define OPTION_FORMAT 1001
+#define OPTION_MD5 1002
+#define OPTION_BATCH_COUNT 1003
+#define OPTION_BATCH_START_AT 1004
+#define OPTION_BATCH_DOUBLE 1005
+#define OPTION_BATCH_INCREMENT 1006
+#define OPTION_BATCH_PROMPT 1007
+
+#define BATCH_COUNT_UNLIMITED -1
+
+static struct option basic_options[] = {
+ {"device-name", required_argument, NULL, 'd'},
+ {"list-devices", no_argument, NULL, 'L'},
+ {"formatted-device-list", required_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"progress", no_argument, NULL, 'p'},
+ {"test", no_argument, NULL, 'T'},
+ {"all-options", no_argument, NULL, 'A'},
+ {"version", no_argument, NULL, 'V'},
+ {"buffer-size", optional_argument, NULL, 'B'},
+ {"batch", optional_argument, NULL, 'b'},
+ {"batch-count", required_argument, NULL, OPTION_BATCH_COUNT},
+ {"batch-start", required_argument, NULL, OPTION_BATCH_START_AT},
+ {"batch-double", no_argument, NULL, OPTION_BATCH_DOUBLE},
+ {"batch-increment", required_argument, NULL, OPTION_BATCH_INCREMENT},
+ {"batch-prompt", no_argument, NULL, OPTION_BATCH_PROMPT},
+ {"format", required_argument, NULL, OPTION_FORMAT},
+ {"accept-md5-only", no_argument, NULL, OPTION_MD5},
+ {"icc-profile", required_argument, NULL, 'i'},
+ {"dont-scan", no_argument, NULL, 'n'},
+ {0, 0, NULL, 0}
+};
+
+#define OUTPUT_PNM 0
+#define OUTPUT_TIFF 1
+
+#define BASE_OPTSTRING "d:hi:Lf:B::nvVTAbp"
+#define STRIP_HEIGHT 256 /* # lines we increment image height */
+
+static struct option *all_options;
+static int option_number_len;
+static int *option_number;
+static SANE_Handle device;
+static int verbose;
+static int progress = 0;
+static int test;
+static int all;
+static int output_format = OUTPUT_PNM;
+static int help;
+static int dont_scan = 0;
+static const char *prog_name;
+static int resolution_optind = -1, resolution_value = 0;
+
+/* window (area) related options */
+static SANE_Option_Descriptor window_option[4]; /*updated descs for x,y,l,t*/
+static int window[4]; /*index into backend options for x,y,l,t*/
+static SANE_Word window_val[2]; /*the value for x,y options*/
+static int window_val_user[2]; /* is x,y user-specified? */
+
+static int accept_only_md5_auth = 0;
+static const char *icc_profile = NULL;
+
+static void fetch_options (SANE_Device * device);
+static void scanimage_exit (void);
+
+static SANE_Word tl_x = 0;
+static SANE_Word tl_y = 0;
+static SANE_Word br_x = 0;
+static SANE_Word br_y = 0;
+static SANE_Byte *buffer;
+static size_t buffer_size;
+
+
+static void
+auth_callback (SANE_String_Const resource,
+ SANE_Char * username, SANE_Char * password)
+{
+ char tmp[3 + 128 + SANE_MAX_USERNAME_LEN + SANE_MAX_PASSWORD_LEN], *wipe;
+ unsigned char md5digest[16];
+ int md5mode = 0, len, query_user = 1;
+ FILE *pass_file;
+ struct stat stat_buf;
+
+ *tmp = 0;
+
+ if (getenv ("HOME") != NULL)
+ {
+ if (strlen (getenv ("HOME")) < 500)
+ {
+ sprintf (tmp, "%s/.sane/pass", getenv ("HOME"));
+ }
+ }
+
+ if ((strlen (tmp) > 0) && (stat (tmp, &stat_buf) == 0))
+ {
+
+ if ((stat_buf.st_mode & 63) != 0)
+ {
+ fprintf (stderr, "%s has wrong permissions (use at least 0600)\n",
+ tmp);
+ }
+ else
+ {
+
+ if ((pass_file = fopen (tmp, "r")) != NULL)
+ {
+
+ if (strstr (resource, "$MD5$") != NULL)
+ len = (strstr (resource, "$MD5$") - resource);
+ else
+ len = strlen (resource);
+
+ while (fgets (tmp, sizeof(tmp), pass_file))
+ {
+
+ if ((strlen (tmp) > 0) && (tmp[strlen (tmp) - 1] == '\n'))
+ tmp[strlen (tmp) - 1] = 0;
+ if ((strlen (tmp) > 0) && (tmp[strlen (tmp) - 1] == '\r'))
+ tmp[strlen (tmp) - 1] = 0;
+
+ if (strchr (tmp, ':') != NULL)
+ {
+
+ if (strchr (strchr (tmp, ':') + 1, ':') != NULL)
+ {
+
+ if ((strncmp
+ (strchr (strchr (tmp, ':') + 1, ':') + 1,
+ resource, len) == 0)
+ &&
+ ((int) strlen
+ (strchr (strchr (tmp, ':') + 1, ':') + 1) ==
+ len))
+ {
+
+ if ((strchr (tmp, ':') - tmp) <
+ SANE_MAX_USERNAME_LEN)
+ {
+
+ if ((strchr (strchr (tmp, ':') + 1, ':') -
+ (strchr (tmp, ':') + 1)) <
+ SANE_MAX_PASSWORD_LEN)
+ {
+
+ strncpy (username, tmp,
+ strchr (tmp, ':') - tmp);
+
+ username[strchr (tmp, ':') - tmp] = 0;
+
+ strncpy (password,
+ strchr (tmp, ':') + 1,
+ strchr (strchr (tmp, ':') + 1,
+ ':') -
+ (strchr (tmp, ':') + 1));
+ password[strchr
+ (strchr (tmp, ':') + 1,
+ ':') - (strchr (tmp,
+ ':') + 1)] =
+ 0;
+
+ query_user = 0;
+ break;
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ fclose (pass_file);
+ }
+ }
+ }
+
+ if (strstr (resource, "$MD5$") != NULL)
+ {
+ md5mode = 1;
+ len = (strstr (resource, "$MD5$") - resource);
+ if (query_user == 1)
+ fprintf (stderr, "Authentification required for resource %*.*s. "
+ "Enter username: ", len, len, resource);
+ }
+ else
+ {
+
+ if (accept_only_md5_auth != 0)
+ {
+ fprintf (stderr, "ERROR: backend requested plain-text password\n");
+ return;
+ }
+ else
+ {
+ fprintf (stderr,
+ "WARNING: backend requested plain-text password\n");
+ query_user = 1;
+ }
+
+ if (query_user == 1)
+ fprintf (stderr,
+ "Authentification required for resource %s. Enter username: ",
+ resource);
+ }
+
+ if (query_user == 1)
+ fgets (username, SANE_MAX_USERNAME_LEN, stdin);
+
+ if ((strlen (username)) && (username[strlen (username) - 1] == '\n'))
+ username[strlen (username) - 1] = 0;
+
+ if (query_user == 1)
+ {
+#ifdef HAVE_GETPASS
+ strcpy (password, (wipe = getpass ("Enter password: ")));
+ memset (wipe, 0, strlen (password));
+#else
+ printf("OS has no getpass(). User Queries will not work\n");
+#endif
+ }
+
+ if (md5mode)
+ {
+
+ sprintf (tmp, "%.128s%.*s", (strstr (resource, "$MD5$")) + 5,
+ SANE_MAX_PASSWORD_LEN - 1, password);
+
+ md5_buffer (tmp, strlen (tmp), md5digest);
+
+ memset (password, 0, SANE_MAX_PASSWORD_LEN);
+
+ sprintf (password, "$MD5$%02x%02x%02x%02x%02x%02x%02x%02x"
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
+ md5digest[0], md5digest[1],
+ md5digest[2], md5digest[3],
+ md5digest[4], md5digest[5],
+ md5digest[6], md5digest[7],
+ md5digest[8], md5digest[9],
+ md5digest[10], md5digest[11],
+ md5digest[12], md5digest[13], md5digest[14], md5digest[15]);
+ }
+}
+
+static RETSIGTYPE
+sighandler (int signum)
+{
+ static SANE_Bool first_time = SANE_TRUE;
+
+ if (device)
+ {
+ fprintf (stderr, "%s: received signal %d\n", prog_name, signum);
+ if (first_time)
+ {
+ first_time = SANE_FALSE;
+ fprintf (stderr, "%s: trying to stop scanner\n", prog_name);
+ sane_cancel (device);
+ }
+ else
+ {
+ fprintf (stderr, "%s: aborting\n", prog_name);
+ _exit (0);
+ }
+ }
+}
+
+static void
+print_unit (SANE_Unit unit)
+{
+ switch (unit)
+ {
+ case SANE_UNIT_NONE:
+ break;
+ case SANE_UNIT_PIXEL:
+ fputs ("pel", stdout);
+ break;
+ case SANE_UNIT_BIT:
+ fputs ("bit", stdout);
+ break;
+ case SANE_UNIT_MM:
+ fputs ("mm", stdout);
+ break;
+ case SANE_UNIT_DPI:
+ fputs ("dpi", stdout);
+ break;
+ case SANE_UNIT_PERCENT:
+ fputc ('%', stdout);
+ break;
+ case SANE_UNIT_MICROSECOND:
+ fputs ("us", stdout);
+ break;
+ }
+}
+
+static void
+print_option (SANE_Device * device, int opt_num, const SANE_Option_Descriptor *opt)
+{
+ const char *str, *last_break, *start;
+ SANE_Bool not_first = SANE_FALSE;
+ int i, column;
+
+ if (opt->type == SANE_TYPE_GROUP){
+ printf (" %s:\n", opt->title);
+ return;
+ }
+
+ /* if both of these are set, option is invalid */
+ if(opt->cap & SANE_CAP_SOFT_SELECT && opt->cap & SANE_CAP_HARD_SELECT){
+ fprintf (stderr, "%s: invalid option caps, SS+HS\n", prog_name);
+ return;
+ }
+
+ /* invalid to select but not detect */
+ if(opt->cap & SANE_CAP_SOFT_SELECT && !(opt->cap & SANE_CAP_SOFT_DETECT)){
+ fprintf (stderr, "%s: invalid option caps, SS!SD\n", prog_name);
+ return;
+ }
+ /* standard allows this, though it makes little sense
+ if(opt->cap & SANE_CAP_HARD_SELECT && !(opt->cap & SANE_CAP_SOFT_DETECT)){
+ fprintf (stderr, "%s: invalid option caps, HS!SD\n", prog_name);
+ return;
+ }*/
+
+ /* if one of these three is not set, option is useless, skip it */
+ if(!(opt->cap &
+ (SANE_CAP_SOFT_SELECT | SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_DETECT)
+ )){
+ return;
+ }
+
+ /* print the option */
+ if ( !strcmp (opt->name, "x")
+ || !strcmp (opt->name, "y")
+ || !strcmp (opt->name, "t")
+ || !strcmp (opt->name, "l"))
+ printf (" -%s", opt->name);
+ else
+ printf (" --%s", opt->name);
+
+ /* print the option choices */
+ if (opt->type == SANE_TYPE_BOOL)
+ {
+ fputs ("[=(", stdout);
+ if (opt->cap & SANE_CAP_AUTOMATIC)
+ fputs ("auto|", stdout);
+ fputs ("yes|no)]", stdout);
+ }
+ else if (opt->type != SANE_TYPE_BUTTON)
+ {
+ fputc (' ', stdout);
+ if (opt->cap & SANE_CAP_AUTOMATIC)
+ {
+ fputs ("auto|", stdout);
+ not_first = SANE_TRUE;
+ }
+ switch (opt->constraint_type)
+ {
+ case SANE_CONSTRAINT_NONE:
+ switch (opt->type)
+ {
+ case SANE_TYPE_INT:
+ fputs ("<int>", stdout);
+ break;
+ case SANE_TYPE_FIXED:
+ fputs ("<float>", stdout);
+ break;
+ case SANE_TYPE_STRING:
+ fputs ("<string>", stdout);
+ break;
+ default:
+ break;
+ }
+ if (opt->type != SANE_TYPE_STRING
+ && opt->size > (SANE_Int) sizeof (SANE_Word))
+ fputs (",...", stdout);
+ break;
+
+ case SANE_CONSTRAINT_RANGE:
+ if (opt->type == SANE_TYPE_INT)
+ {
+ if (!strcmp (opt->name, "x"))
+ {
+ printf ("%d..%d",
+ opt->constraint.range->min,
+ opt->constraint.range->max - tl_x);
+ }
+ else if (!strcmp (opt->name, "y"))
+ {
+ printf ("%d..%d",
+ opt->constraint.range->min,
+ opt->constraint.range->max - tl_y);
+ }
+ else
+ {
+ printf ("%d..%d",
+ opt->constraint.range->min,
+ opt->constraint.range->max);
+ }
+ print_unit (opt->unit);
+ if (opt->size > (SANE_Int) sizeof (SANE_Word))
+ fputs (",...", stdout);
+ if (opt->constraint.range->quant)
+ printf (" (in steps of %d)", opt->constraint.range->quant);
+ }
+ else
+ {
+ if (!strcmp (opt->name, "x"))
+ {
+ printf ("%g..%g",
+ SANE_UNFIX (opt->constraint.range->min),
+ SANE_UNFIX (opt->constraint.range->max - tl_x));
+ }
+ else if (!strcmp (opt->name, "y"))
+ {
+ printf ("%g..%g",
+ SANE_UNFIX (opt->constraint.range->min),
+ SANE_UNFIX (opt->constraint.range->max - tl_y));
+ }
+ else
+ {
+ printf ("%g..%g",
+ SANE_UNFIX (opt->constraint.range->min),
+ SANE_UNFIX (opt->constraint.range->max));
+ }
+ print_unit (opt->unit);
+ if (opt->size > (SANE_Int) sizeof (SANE_Word))
+ fputs (",...", stdout);
+ if (opt->constraint.range->quant)
+ printf (" (in steps of %g)",
+ SANE_UNFIX (opt->constraint.range->quant));
+ }
+ break;
+
+ case SANE_CONSTRAINT_WORD_LIST:
+ for (i = 0; i < opt->constraint.word_list[0]; ++i)
+ {
+ if (not_first)
+ fputc ('|', stdout);
+
+ not_first = SANE_TRUE;
+
+ if (opt->type == SANE_TYPE_INT)
+ printf ("%d", opt->constraint.word_list[i + 1]);
+ else
+ printf ("%g", SANE_UNFIX (opt->constraint.word_list[i + 1]));
+ }
+ print_unit (opt->unit);
+ if (opt->size > (SANE_Int) sizeof (SANE_Word))
+ fputs (",...", stdout);
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ for (i = 0; opt->constraint.string_list[i]; ++i)
+ {
+ if (i > 0)
+ fputc ('|', stdout);
+
+ fputs (opt->constraint.string_list[i], stdout);
+ }
+ break;
+ }
+ }
+
+ /* print current option value */
+ if (opt->type == SANE_TYPE_STRING || opt->size == sizeof (SANE_Word))
+ {
+ if (SANE_OPTION_IS_ACTIVE (opt->cap))
+ {
+ void *val = alloca (opt->size);
+ sane_control_option (device, opt_num, SANE_ACTION_GET_VALUE, val,
+ 0);
+ fputs (" [", stdout);
+ switch (opt->type)
+ {
+ case SANE_TYPE_BOOL:
+ fputs (*(SANE_Bool *) val ? "yes" : "no", stdout);
+ break;
+
+ case SANE_TYPE_INT:
+ if (strcmp (opt->name, "l") == 0)
+ {
+ tl_x = (*(SANE_Fixed *) val);
+ printf ("%d", tl_x);
+ }
+ else if (strcmp (opt->name, "t") == 0)
+ {
+ tl_y = (*(SANE_Fixed *) val);
+ printf ("%d", tl_y);
+ }
+ else if (strcmp (opt->name, "x") == 0)
+ {
+ br_x = (*(SANE_Fixed *) val);
+ printf ("%d", br_x - tl_x);
+ }
+ else if (strcmp (opt->name, "y") == 0)
+ {
+ br_y = (*(SANE_Fixed *) val);
+ printf ("%d", br_y - tl_y);
+ }
+ else
+ printf ("%d", *(SANE_Int *) val);
+ break;
+
+ case SANE_TYPE_FIXED:
+
+ if (strcmp (opt->name, "l") == 0)
+ {
+ tl_x = (*(SANE_Fixed *) val);
+ printf ("%g", SANE_UNFIX (tl_x));
+ }
+ else if (strcmp (opt->name, "t") == 0)
+ {
+ tl_y = (*(SANE_Fixed *) val);
+ printf ("%g", SANE_UNFIX (tl_y));
+ }
+ else if (strcmp (opt->name, "x") == 0)
+ {
+ br_x = (*(SANE_Fixed *) val);
+ printf ("%g", SANE_UNFIX (br_x - tl_x));
+ }
+ else if (strcmp (opt->name, "y") == 0)
+ {
+ br_y = (*(SANE_Fixed *) val);
+ printf ("%g", SANE_UNFIX (br_y - tl_y));
+ }
+ else
+ printf ("%g", SANE_UNFIX (*(SANE_Fixed *) val));
+
+ break;
+
+ case SANE_TYPE_STRING:
+ fputs ((char *) val, stdout);
+ break;
+
+ default:
+ break;
+ }
+ fputc (']', stdout);
+ }
+ }
+
+ if (!SANE_OPTION_IS_ACTIVE (opt->cap))
+ fputs (" [inactive]", stdout);
+
+ else if(opt->cap & SANE_CAP_HARD_SELECT)
+ fputs (" [hardware]", stdout);
+
+ else if(!(opt->cap & SANE_CAP_SOFT_SELECT) && opt->cap & SANE_CAP_SOFT_DETECT)
+ fputs (" [read-only]", stdout);
+
+ fputs ("\n ", stdout);
+
+ column = 8;
+ last_break = 0;
+ start = opt->desc;
+ for (str = opt->desc; *str; ++str)
+ {
+ ++column;
+ if (*str == ' ')
+ last_break = str;
+ else if (*str == '\n'){
+ column=80;
+ last_break = str;
+ }
+ if (column >= 79 && last_break)
+ {
+ while (start < last_break)
+ fputc (*start++, stdout);
+ start = last_break + 1; /* skip blank */
+ fputs ("\n ", stdout);
+ column = 8 + (str - start);
+ }
+ }
+ while (*start)
+ fputc (*start++, stdout);
+ fputc ('\n', stdout);
+}
+
+/* A scalar has the following syntax:
+
+ V [ U ]
+
+ V is the value of the scalar. It is either an integer or a
+ floating point number, depending on the option type.
+
+ U is an optional unit. If not specified, the default unit is used.
+ The following table lists which units are supported depending on
+ what the option's default unit is:
+
+ Option's unit: Allowed units:
+
+ SANE_UNIT_NONE:
+ SANE_UNIT_PIXEL: pel
+ SANE_UNIT_BIT: b (bit), B (byte)
+ SANE_UNIT_MM: mm (millimeter), cm (centimeter), in or " (inches),
+ SANE_UNIT_DPI: dpi
+ SANE_UNIT_PERCENT: %
+ SANE_UNIT_PERCENT: us
+ */
+static const char *
+parse_scalar (const SANE_Option_Descriptor * opt, const char *str,
+ SANE_Word * value)
+{
+ char *end;
+ double v;
+
+ if (opt->type == SANE_TYPE_FIXED)
+ v = strtod (str, &end) * (1 << SANE_FIXED_SCALE_SHIFT);
+ else
+ v = strtol (str, &end, 10);
+
+ if (str == end)
+ {
+ fprintf (stderr,
+ "%s: option --%s: bad option value (rest of option: %s)\n",
+ prog_name, opt->name, str);
+ exit (1);
+ }
+ str = end;
+
+ switch (opt->unit)
+ {
+ case SANE_UNIT_NONE:
+ case SANE_UNIT_PIXEL:
+ break;
+
+ case SANE_UNIT_BIT:
+ if (*str == 'b' || *str == 'B')
+ {
+ if (*str++ == 'B')
+ v *= 8;
+ }
+ break;
+
+ case SANE_UNIT_MM:
+ if (str[0] == '\0')
+ v *= 1.0; /* default to mm */
+ else if (strcmp (str, "mm") == 0)
+ str += sizeof ("mm") - 1;
+ else if (strcmp (str, "cm") == 0)
+ {
+ str += sizeof ("cm") - 1;
+ v *= 10.0;
+ }
+ else if (strcmp (str, "in") == 0 || *str == '"')
+ {
+ if (*str++ != '"')
+ ++str;
+ v *= 25.4; /* 25.4 mm/inch */
+ }
+ else
+ {
+ fprintf (stderr,
+ "%s: option --%s: illegal unit (rest of option: %s)\n",
+ prog_name, opt->name, str);
+ return 0;
+ }
+ break;
+
+ case SANE_UNIT_DPI:
+ if (strcmp (str, "dpi") == 0)
+ str += sizeof ("dpi") - 1;
+ break;
+
+ case SANE_UNIT_PERCENT:
+ if (*str == '%')
+ ++str;
+ break;
+
+ case SANE_UNIT_MICROSECOND:
+ if (strcmp (str, "us") == 0)
+ str += sizeof ("us") - 1;
+ break;
+ }
+
+ if(v < 0){
+ *value = v - 0.5;
+ }
+ else{
+ *value = v + 0.5;
+ }
+
+ return str;
+}
+
+/* A vector has the following syntax:
+
+ [ '[' I ']' ] S { [','|'-'] [ '[' I ']' S }
+
+ The number in brackets (I), if present, determines the index of the
+ vector element to be set next. If I is not present, the value of
+ last index used plus 1 is used. The first index value used is 0
+ unless I is present.
+
+ S is a scalar value as defined by parse_scalar().
+
+ If two consecutive value specs are separated by a comma (,) their
+ values are set independently. If they are separated by a dash (-),
+ they define the endpoints of a line and all vector values between
+ the two endpoints are set according to the value of the
+ interpolated line. For example, [0]15-[255]15 defines a vector of
+ 256 elements whose value is 15. Similarly, [0]0-[255]255 defines a
+ vector of 256 elements whose value starts at 0 and increases to
+ 255. */
+static void
+parse_vector (const SANE_Option_Descriptor * opt, const char *str,
+ SANE_Word * vector, size_t vector_length)
+{
+ SANE_Word value, prev_value = 0;
+ int index = -1, prev_index = 0;
+ char *end, separator = '\0';
+
+ /* initialize vector to all zeroes: */
+ memset (vector, 0, vector_length * sizeof (SANE_Word));
+
+ do
+ {
+ if (*str == '[')
+ {
+ /* read index */
+ index = strtol (++str, &end, 10);
+ if (str == end || *end != ']')
+ {
+ fprintf (stderr, "%s: option --%s: closing bracket missing "
+ "(rest of option: %s)\n", prog_name, opt->name, str);
+ exit (1);
+ }
+ str = end + 1;
+ }
+ else
+ ++index;
+
+ if (index < 0 || index >= (int) vector_length)
+ {
+ fprintf (stderr,
+ "%s: option --%s: index %d out of range [0..%ld]\n",
+ prog_name, opt->name, index, (long) vector_length - 1);
+ exit (1);
+ }
+
+ /* read value */
+ str = parse_scalar (opt, str, &value);
+ if (!str)
+ exit (1);
+
+ if (*str && *str != '-' && *str != ',')
+ {
+ fprintf (stderr,
+ "%s: option --%s: illegal separator (rest of option: %s)\n",
+ prog_name, opt->name, str);
+ exit (1);
+ }
+
+ /* store value: */
+ vector[index] = value;
+ if (separator == '-')
+ {
+ /* interpolate */
+ double v, slope;
+ int i;
+
+ v = (double) prev_value;
+ slope = ((double) value - v) / (index - prev_index);
+
+ for (i = prev_index + 1; i < index; ++i)
+ {
+ v += slope;
+ vector[i] = (SANE_Word) v;
+ }
+ }
+
+ prev_index = index;
+ prev_value = value;
+ separator = *str++;
+ }
+ while (separator == ',' || separator == '-');
+
+ if (verbose > 2)
+ {
+ int i;
+
+ fprintf (stderr, "%s: value for --%s is: ", prog_name, opt->name);
+ for (i = 0; i < (int) vector_length; ++i)
+ if (opt->type == SANE_TYPE_FIXED)
+ fprintf (stderr, "%g ", SANE_UNFIX (vector[i]));
+ else
+ fprintf (stderr, "%d ", vector[i]);
+ fputc ('\n', stderr);
+ }
+}
+
+static void
+fetch_options (SANE_Device * device)
+{
+ const SANE_Option_Descriptor *opt;
+ SANE_Int num_dev_options;
+ int i, option_count;
+ SANE_Status status;
+
+ opt = sane_get_option_descriptor (device, 0);
+ if (opt == NULL)
+ {
+ fprintf (stderr, "Could not get option descriptor for option 0\n");
+ exit (1);
+ }
+
+ status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE,
+ &num_dev_options, 0);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "Could not get value for option 0: %s\n",
+ sane_strstatus (status));
+ exit (1);
+ }
+
+ /* build the full table of long options */
+ option_count = 0;
+ for (i = 1; i < num_dev_options; ++i)
+ {
+ opt = sane_get_option_descriptor (device, i);
+ if (opt == NULL)
+ {
+ fprintf (stderr, "Could not get option descriptor for option %d\n",i);
+ exit (1);
+ }
+
+ /* create command line option only for settable options */
+ if (!SANE_OPTION_IS_SETTABLE (opt->cap) || opt->type == SANE_TYPE_GROUP)
+ continue;
+
+ option_number[option_count] = i;
+
+ all_options[option_count].name = (const char *) opt->name;
+ all_options[option_count].flag = 0;
+ all_options[option_count].val = 0;
+
+ if (opt->type == SANE_TYPE_BOOL)
+ all_options[option_count].has_arg = optional_argument;
+ else if (opt->type == SANE_TYPE_BUTTON)
+ all_options[option_count].has_arg = no_argument;
+ else
+ all_options[option_count].has_arg = required_argument;
+
+ /* Look for scan resolution */
+ if ((opt->type == SANE_TYPE_FIXED || opt->type == SANE_TYPE_INT)
+ && opt->size == sizeof (SANE_Int)
+ && (opt->unit == SANE_UNIT_DPI)
+ && (strcmp (opt->name, SANE_NAME_SCAN_RESOLUTION) == 0))
+ resolution_optind = i;
+
+ /* Keep track of top-left corner options (if they exist at
+ all) and replace the bottom-right corner options by a
+ width/height option (if they exist at all). */
+ if ((opt->type == SANE_TYPE_FIXED || opt->type == SANE_TYPE_INT)
+ && opt->size == sizeof (SANE_Int)
+ && (opt->unit == SANE_UNIT_MM || opt->unit == SANE_UNIT_PIXEL))
+ {
+ if (strcmp (opt->name, SANE_NAME_SCAN_BR_X) == 0)
+ {
+ window[0] = i;
+ all_options[option_count].name = "width";
+ all_options[option_count].val = 'x';
+ window_option[0] = *opt;
+ window_option[0].title = "Scan width";
+ window_option[0].desc = "Width of scan-area.";
+ window_option[0].name = "x";
+ }
+ else if (strcmp (opt->name, SANE_NAME_SCAN_BR_Y) == 0)
+ {
+ window[1] = i;
+ all_options[option_count].name = "height";
+ all_options[option_count].val = 'y';
+ window_option[1] = *opt;
+ window_option[1].title = "Scan height";
+ window_option[1].desc = "Height of scan-area.";
+ window_option[1].name = "y";
+ }
+ else if (strcmp (opt->name, SANE_NAME_SCAN_TL_X) == 0)
+ {
+ window[2] = i;
+ all_options[option_count].val = 'l';
+ window_option[2] = *opt;
+ window_option[2].name = "l";
+ }
+ else if (strcmp (opt->name, SANE_NAME_SCAN_TL_Y) == 0)
+ {
+ window[3] = i;
+ all_options[option_count].val = 't';
+ window_option[3] = *opt;
+ window_option[3].name = "t";
+ }
+ }
+ ++option_count;
+ }
+ memcpy (all_options + option_count, basic_options, sizeof (basic_options));
+ option_count += NELEMS (basic_options);
+ memset (all_options + option_count, 0, sizeof (all_options[0]));
+
+ /* Initialize width & height options based on backend default
+ values for top-left x/y and bottom-right x/y: */
+ for (i = 0; i < 2; ++i)
+ {
+ if (window[i] && !window_val_user[i])
+ {
+ sane_control_option (device, window[i],
+ SANE_ACTION_GET_VALUE, &window_val[i], 0);
+ if (window[i + 2]){
+ SANE_Word pos;
+ sane_control_option (device, window[i + 2],
+ SANE_ACTION_GET_VALUE, &pos, 0);
+ window_val[i] -= pos;
+ }
+ }
+ }
+}
+
+static void
+set_option (SANE_Handle device, int optnum, void *valuep)
+{
+ const SANE_Option_Descriptor *opt;
+ SANE_Status status;
+ SANE_Word orig = 0;
+ SANE_Int info = 0;
+
+ opt = sane_get_option_descriptor (device, optnum);
+ if (opt && (!SANE_OPTION_IS_ACTIVE (opt->cap)))
+ {
+ if (verbose > 0)
+ fprintf (stderr, "%s: ignored request to set inactive option %s\n",
+ prog_name, opt->name);
+ return;
+ }
+
+ if (opt->size == sizeof (SANE_Word) && opt->type != SANE_TYPE_STRING)
+ orig = *(SANE_Word *) valuep;
+
+ status = sane_control_option (device, optnum, SANE_ACTION_SET_VALUE,
+ valuep, &info);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: setting of option --%s failed (%s)\n",
+ prog_name, opt->name, sane_strstatus (status));
+ exit (1);
+ }
+
+ if ((info & SANE_INFO_INEXACT) && opt->size == sizeof (SANE_Word))
+ {
+ if (opt->type == SANE_TYPE_INT)
+ fprintf (stderr, "%s: rounded value of %s from %d to %d\n",
+ prog_name, opt->name, orig, *(SANE_Word *) valuep);
+ else if (opt->type == SANE_TYPE_FIXED)
+ fprintf (stderr, "%s: rounded value of %s from %g to %g\n",
+ prog_name, opt->name,
+ SANE_UNFIX (orig), SANE_UNFIX (*(SANE_Word *) valuep));
+ }
+
+ if (info & SANE_INFO_RELOAD_OPTIONS)
+ fetch_options (device);
+}
+
+static void
+process_backend_option (SANE_Handle device, int optnum, const char *optarg)
+{
+ static SANE_Word *vector = 0;
+ static size_t vector_size = 0;
+ const SANE_Option_Descriptor *opt;
+ size_t vector_length;
+ SANE_Status status;
+ SANE_Word value;
+ void *valuep;
+
+ opt = sane_get_option_descriptor (device, optnum);
+
+ if (!SANE_OPTION_IS_ACTIVE (opt->cap))
+ {
+ fprintf (stderr, "%s: attempted to set inactive option %s\n",
+ prog_name, opt->name);
+ exit (1);
+ }
+
+ if ((opt->cap & SANE_CAP_AUTOMATIC) && optarg &&
+ strncasecmp (optarg, "auto", 4) == 0)
+ {
+ status = sane_control_option (device, optnum, SANE_ACTION_SET_AUTO,
+ 0, 0);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr,
+ "%s: failed to set option --%s to automatic (%s)\n",
+ prog_name, opt->name, sane_strstatus (status));
+ exit (1);
+ }
+ return;
+ }
+
+ valuep = &value;
+ switch (opt->type)
+ {
+ case SANE_TYPE_BOOL:
+ value = 1; /* no argument means option is set */
+ if (optarg)
+ {
+ if (strncasecmp (optarg, "yes", strlen (optarg)) == 0)
+ value = 1;
+ else if (strncasecmp (optarg, "no", strlen (optarg)) == 0)
+ value = 0;
+ else
+ {
+ fprintf (stderr, "%s: option --%s: bad option value `%s'\n",
+ prog_name, opt->name, optarg);
+ exit (1);
+ }
+ }
+ break;
+
+ case SANE_TYPE_INT:
+ case SANE_TYPE_FIXED:
+ /* ensure vector is long enough: */
+ vector_length = opt->size / sizeof (SANE_Word);
+ if (vector_size < vector_length)
+ {
+ vector_size = vector_length;
+ vector = realloc (vector, vector_length * sizeof (SANE_Word));
+ if (!vector)
+ {
+ fprintf (stderr, "%s: out of memory\n", prog_name);
+ exit (1);
+ }
+ }
+ parse_vector (opt, optarg, vector, vector_length);
+ valuep = vector;
+ break;
+
+ case SANE_TYPE_STRING:
+ valuep = malloc (opt->size);
+ if (!valuep)
+ {
+ fprintf (stderr, "%s: out of memory\n", prog_name);
+ exit (1);
+ }
+ strncpy (valuep, optarg, opt->size);
+ ((char *) valuep)[opt->size - 1] = 0;
+ break;
+
+ case SANE_TYPE_BUTTON:
+ value = 0; /* value doesn't matter */
+ break;
+
+ default:
+ fprintf (stderr, "%s: duh, got unknown option type %d\n",
+ prog_name, opt->type);
+ return;
+ }
+ set_option (device, optnum, valuep);
+}
+
+static void
+write_pnm_header (SANE_Frame format, int width, int height, int depth)
+{
+ /* The netpbm-package does not define raw image data with maxval > 255. */
+ /* But writing maxval 65535 for 16bit data gives at least a chance */
+ /* to read the image. */
+ switch (format)
+ {
+ case SANE_FRAME_RED:
+ case SANE_FRAME_GREEN:
+ case SANE_FRAME_BLUE:
+ case SANE_FRAME_RGB:
+ printf ("P6\n# SANE data follows\n%d %d\n%d\n", width, height,
+ (depth <= 8) ? 255 : 65535);
+ break;
+
+ default:
+ if (depth == 1)
+ printf ("P4\n# SANE data follows\n%d %d\n", width, height);
+ else
+ printf ("P5\n# SANE data follows\n%d %d\n%d\n", width, height,
+ (depth <= 8) ? 255 : 65535);
+ break;
+ }
+#ifdef __EMX__ /* OS2 - write in binary mode. */
+ _fsetmode (stdout, "b");
+#endif
+}
+
+static void *
+advance (Image * image)
+{
+ if (++image->x >= image->width)
+ {
+ image->x = 0;
+ if (++image->y >= image->height || !image->data)
+ {
+ size_t old_size = 0, new_size;
+
+ if (image->data)
+ old_size = image->height * image->width;
+
+ image->height += STRIP_HEIGHT;
+ new_size = image->height * image->width;
+
+ if (image->data)
+ image->data = realloc (image->data, new_size);
+ else
+ image->data = malloc (new_size);
+ if (image->data)
+ memset (image->data + old_size, 0, new_size - old_size);
+ }
+ }
+ if (!image->data)
+ fprintf (stderr, "%s: can't allocate image buffer (%dx%d)\n",
+ prog_name, image->width, image->height);
+ return image->data;
+}
+
+static SANE_Status
+scan_it (void)
+{
+ int i, len, first_frame = 1, offset = 0, must_buffer = 0, hundred_percent;
+ SANE_Byte min = 0xff, max = 0;
+ SANE_Parameters parm;
+ SANE_Status status;
+ Image image = { 0, 0, 0, 0, 0 };
+ static const char *format_name[] = {
+ "gray", "RGB", "red", "green", "blue"
+ };
+ SANE_Word total_bytes = 0, expected_bytes;
+ SANE_Int hang_over = -1;
+
+ do
+ {
+ if (!first_frame)
+ {
+#ifdef SANE_STATUS_WARMING_UP
+ do
+ {
+ status = sane_start (device);
+ }
+ while(status == SANE_STATUS_WARMING_UP);
+#else
+ status = sane_start (device);
+#endif
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_start: %s\n",
+ prog_name, sane_strstatus (status));
+ goto cleanup;
+ }
+ }
+
+ status = sane_get_parameters (device, &parm);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_get_parameters: %s\n",
+ prog_name, sane_strstatus (status));
+ goto cleanup;
+ }
+
+ if (verbose)
+ {
+ if (first_frame)
+ {
+ if (parm.lines >= 0)
+ fprintf (stderr, "%s: scanning image of size %dx%d pixels at "
+ "%d bits/pixel\n",
+ prog_name, parm.pixels_per_line, parm.lines,
+ 8 * parm.bytes_per_line / parm.pixels_per_line);
+ else
+ fprintf (stderr, "%s: scanning image %d pixels wide and "
+ "variable height at %d bits/pixel\n",
+ prog_name, parm.pixels_per_line,
+ 8 * parm.bytes_per_line / parm.pixels_per_line);
+ }
+
+ fprintf (stderr, "%s: acquiring %s frame\n", prog_name,
+ parm.format <= SANE_FRAME_BLUE ? format_name[parm.format]:"Unknown");
+ }
+
+ if (first_frame)
+ {
+ switch (parm.format)
+ {
+ case SANE_FRAME_RED:
+ case SANE_FRAME_GREEN:
+ case SANE_FRAME_BLUE:
+ assert (parm.depth == 8);
+ must_buffer = 1;
+ offset = parm.format - SANE_FRAME_RED;
+ break;
+
+ case SANE_FRAME_RGB:
+ assert ((parm.depth == 8) || (parm.depth == 16));
+ case SANE_FRAME_GRAY:
+ assert ((parm.depth == 1) || (parm.depth == 8)
+ || (parm.depth == 16));
+ if (parm.lines < 0)
+ {
+ must_buffer = 1;
+ offset = 0;
+ }
+ else
+ {
+ if (output_format == OUTPUT_TIFF)
+ sanei_write_tiff_header (parm.format,
+ parm.pixels_per_line, parm.lines,
+ parm.depth, resolution_value,
+ icc_profile);
+ else
+ write_pnm_header (parm.format, parm.pixels_per_line,
+ parm.lines, parm.depth);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (must_buffer)
+ {
+ /* We're either scanning a multi-frame image or the
+ scanner doesn't know what the eventual image height
+ will be (common for hand-held scanners). In either
+ case, we need to buffer all data before we can write
+ the image. */
+ image.width = parm.bytes_per_line;
+
+ if (parm.lines >= 0)
+ /* See advance(); we allocate one extra line so we
+ don't end up realloc'ing in when the image has been
+ filled in. */
+ image.height = parm.lines - STRIP_HEIGHT + 1;
+ else
+ image.height = 0;
+
+ image.x = image.width - 1;
+ image.y = -1;
+ if (!advance (&image))
+ {
+ status = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+ }
+ }
+ else
+ {
+ assert (parm.format >= SANE_FRAME_RED
+ && parm.format <= SANE_FRAME_BLUE);
+ offset = parm.format - SANE_FRAME_RED;
+ image.x = image.y = 0;
+ }
+ hundred_percent = parm.bytes_per_line * parm.lines
+ * ((parm.format == SANE_FRAME_RGB || parm.format == SANE_FRAME_GRAY) ? 1:3);
+
+ while (1)
+ {
+ double progr;
+ status = sane_read (device, buffer, buffer_size, &len);
+ total_bytes += (SANE_Word) len;
+ progr = ((total_bytes * 100.) / (double) hundred_percent);
+ if (progr > 100.)
+ progr = 100.;
+ if (progress)
+ fprintf (stderr, "Progress: %3.1f%%\r", progr);
+
+ if (status != SANE_STATUS_GOOD)
+ {
+ if (verbose && parm.depth == 8)
+ fprintf (stderr, "%s: min/max graylevel value = %d/%d\n",
+ prog_name, min, max);
+ if (status != SANE_STATUS_EOF)
+ {
+ fprintf (stderr, "%s: sane_read: %s\n",
+ prog_name, sane_strstatus (status));
+ return status;
+ }
+ break;
+ }
+
+ if (must_buffer)
+ {
+ switch (parm.format)
+ {
+ case SANE_FRAME_RED:
+ case SANE_FRAME_GREEN:
+ case SANE_FRAME_BLUE:
+ for (i = 0; i < len; ++i)
+ {
+ image.data[offset + 3 * i] = buffer[i];
+ if (!advance (&image))
+ {
+ status = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+ }
+ offset += 3 * len;
+ break;
+
+ case SANE_FRAME_RGB:
+ for (i = 0; i < len; ++i)
+ {
+ image.data[offset + i] = buffer[i];
+ if (!advance (&image))
+ {
+ status = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+ }
+ offset += len;
+ break;
+
+ case SANE_FRAME_GRAY:
+ for (i = 0; i < len; ++i)
+ {
+ image.data[offset + i] = buffer[i];
+ if (!advance (&image))
+ {
+ status = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+ }
+ offset += len;
+ break;
+
+ default:
+ break;
+ }
+ }
+ else /* ! must_buffer */
+ {
+ if ((output_format == OUTPUT_TIFF) || (parm.depth != 16))
+ fwrite (buffer, 1, len, stdout);
+ else
+ {
+#if !defined(WORDS_BIGENDIAN)
+ int i, start = 0;
+
+ /* check if we have saved one byte from the last sane_read */
+ if (hang_over > -1)
+ {
+ if (len > 0)
+ {
+ fwrite (buffer, 1, 1, stdout);
+ buffer[0] = (SANE_Byte) hang_over;
+ hang_over = -1;
+ start = 1;
+ }
+ }
+ /* now do the byte-swapping */
+ for (i = start; i < (len - 1); i += 2)
+ {
+ unsigned char LSB;
+ LSB = buffer[i];
+ buffer[i] = buffer[i + 1];
+ buffer[i + 1] = LSB;
+ }
+ /* check if we have an odd number of bytes */
+ if (((len - start) % 2) != 0)
+ {
+ hang_over = buffer[len - 1];
+ len--;
+ }
+#endif
+ fwrite (buffer, 1, len, stdout);
+ }
+ }
+
+ if (verbose && parm.depth == 8)
+ {
+ for (i = 0; i < len; ++i)
+ if (buffer[i] >= max)
+ max = buffer[i];
+ else if (buffer[i] < min)
+ min = buffer[i];
+ }
+ }
+ first_frame = 0;
+ }
+ while (!parm.last_frame);
+
+ if (must_buffer)
+ {
+ image.height = image.y;
+
+ if (output_format == OUTPUT_TIFF)
+ sanei_write_tiff_header (parm.format, parm.pixels_per_line,
+ image.height, parm.depth, resolution_value,
+ icc_profile);
+ else
+ write_pnm_header (parm.format, parm.pixels_per_line,
+ image.height, parm.depth);
+
+#if !defined(WORDS_BIGENDIAN)
+ /* multibyte pnm file may need byte swap to LE */
+ /* FIXME: other bit depths? */
+ if (output_format != OUTPUT_TIFF && parm.depth == 16)
+ {
+ int i;
+ for (i = 0; i < image.height * image.width; i += 2)
+ {
+ unsigned char LSB;
+ LSB = image.data[i];
+ image.data[i] = image.data[i + 1];
+ image.data[i + 1] = LSB;
+ }
+ }
+#endif
+
+ fwrite (image.data, 1, image.height * image.width, stdout);
+ }
+
+ /* flush the output buffer */
+ fflush( stdout );
+
+cleanup:
+ if (image.data)
+ free (image.data);
+
+
+ expected_bytes = parm.bytes_per_line * parm.lines *
+ ((parm.format == SANE_FRAME_RGB
+ || parm.format == SANE_FRAME_GRAY) ? 1 : 3);
+ if (parm.lines < 0)
+ expected_bytes = 0;
+ if (total_bytes > expected_bytes && expected_bytes != 0)
+ {
+ fprintf (stderr,
+ "%s: WARNING: read more data than announced by backend "
+ "(%u/%u)\n", prog_name, total_bytes, expected_bytes);
+ }
+ else if (verbose)
+ fprintf (stderr, "%s: read %u bytes in total\n", prog_name, total_bytes);
+
+ return status;
+}
+
+#define clean_buffer(buf,size) memset ((buf), 0x23, size)
+
+static void
+pass_fail (int max, int len, SANE_Byte * buffer, SANE_Status status)
+{
+ if (status != SANE_STATUS_GOOD)
+ fprintf (stderr, "FAIL Error: %s\n", sane_strstatus (status));
+ else if (buffer[len] != 0x23)
+ {
+ while (len <= max && buffer[len] != 0x23)
+ ++len;
+ fprintf (stderr, "FAIL Cheat: %d bytes\n", len);
+ }
+ else if (len > max)
+ fprintf (stderr, "FAIL Overflow: %d bytes\n", len);
+ else if (len == 0)
+ fprintf (stderr, "FAIL No data\n");
+ else
+ fprintf (stderr, "PASS\n");
+}
+
+static SANE_Status
+test_it (void)
+{
+ int i, len;
+ SANE_Parameters parm;
+ SANE_Status status;
+ Image image = { 0, 0, 0, 0, 0 };
+ static const char *format_name[] =
+ { "gray", "RGB", "red", "green", "blue" };
+
+#ifdef SANE_STATUS_WARMING_UP
+ do
+ {
+ status = sane_start (device);
+ }
+ while(status == SANE_STATUS_WARMING_UP);
+#else
+ status = sane_start (device);
+#endif
+
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_start: %s\n",
+ prog_name, sane_strstatus (status));
+ goto cleanup;
+ }
+
+ status = sane_get_parameters (device, &parm);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_get_parameters: %s\n",
+ prog_name, sane_strstatus (status));
+ goto cleanup;
+ }
+
+ if (parm.lines >= 0)
+ fprintf (stderr, "%s: scanning image of size %dx%d pixels at "
+ "%d bits/pixel\n", prog_name, parm.pixels_per_line, parm.lines,
+ 8 * parm.bytes_per_line / parm.pixels_per_line);
+ else
+ fprintf (stderr, "%s: scanning image %d pixels wide and "
+ "variable height at %d bits/pixel\n",
+ prog_name, parm.pixels_per_line,
+ 8 * parm.bytes_per_line / parm.pixels_per_line);
+ fprintf (stderr, "%s: acquiring %s frame, %d bits/sample\n", prog_name,
+ parm.format <= SANE_FRAME_BLUE ? format_name[parm.format]:"Unknown",
+ parm.depth);
+
+ image.data = malloc (parm.bytes_per_line * 2);
+
+ clean_buffer (image.data, parm.bytes_per_line * 2);
+ fprintf (stderr, "%s: reading one scanline, %d bytes...\t", prog_name,
+ parm.bytes_per_line);
+ status = sane_read (device, image.data, parm.bytes_per_line, &len);
+ pass_fail (parm.bytes_per_line, len, image.data, status);
+ if (status != SANE_STATUS_GOOD)
+ goto cleanup;
+
+ clean_buffer (image.data, parm.bytes_per_line * 2);
+ fprintf (stderr, "%s: reading one byte...\t\t", prog_name);
+ status = sane_read (device, image.data, 1, &len);
+ pass_fail (1, len, image.data, status);
+ if (status != SANE_STATUS_GOOD)
+ goto cleanup;
+
+ for (i = 2; i < parm.bytes_per_line * 2; i *= 2)
+ {
+ clean_buffer (image.data, parm.bytes_per_line * 2);
+ fprintf (stderr, "%s: stepped read, %d bytes... \t", prog_name, i);
+ status = sane_read (device, image.data, i, &len);
+ pass_fail (i, len, image.data, status);
+ if (status != SANE_STATUS_GOOD)
+ goto cleanup;
+ }
+
+ for (i /= 2; i > 2; i /= 2)
+ {
+ clean_buffer (image.data, parm.bytes_per_line * 2);
+ fprintf (stderr, "%s: stepped read, %d bytes... \t", prog_name, i - 1);
+ status = sane_read (device, image.data, i - 1, &len);
+ pass_fail (i - 1, len, image.data, status);
+ if (status != SANE_STATUS_GOOD)
+ goto cleanup;
+ }
+
+cleanup:
+ sane_cancel (device);
+ if (image.data)
+ free (image.data);
+ return status;
+}
+
+
+static int
+get_resolution (void)
+{
+ const SANE_Option_Descriptor *resopt;
+ int resol = 0;
+ void *val;
+
+ if (resolution_optind < 0)
+ return 0;
+ resopt = sane_get_option_descriptor (device, resolution_optind);
+ if (!resopt)
+ return 0;
+
+ val = alloca (resopt->size);
+ if (!val)
+ return 0;
+
+ sane_control_option (device, resolution_optind, SANE_ACTION_GET_VALUE, val,
+ 0);
+ if (resopt->type == SANE_TYPE_INT)
+ resol = *(SANE_Int *) val;
+ else
+ resol = (int) (SANE_UNFIX (*(SANE_Fixed *) val) + 0.5);
+
+ return resol;
+}
+
+static void
+scanimage_exit (void)
+{
+ if (device)
+ {
+ if (verbose > 1)
+ fprintf (stderr, "Closing device\n");
+ sane_close (device);
+ }
+ if (verbose > 1)
+ fprintf (stderr, "Calling sane_exit\n");
+ sane_exit ();
+
+ if (all_options)
+ free (all_options);
+ if (option_number)
+ free (option_number);
+ if (verbose > 1)
+ fprintf (stderr, "scanimage: finished\n");
+}
+
+/** @brief print device options to stdout
+ *
+ * @param device struct of the opened device to describe
+ * @param num_dev_options number of device options
+ * @param ro SANE_TRUE to print read-only options
+ */
+static void print_options(SANE_Device * device, SANE_Int num_dev_options, SANE_Bool ro)
+{
+ int i, j;
+ const SANE_Option_Descriptor *opt;
+
+ for (i = 1; i < num_dev_options; ++i)
+ {
+ opt = 0;
+
+ /* scan area uses modified option struct */
+ for (j = 0; j < 4; ++j)
+ if (i == window[j])
+ opt = window_option + j;
+
+ if (!opt)
+ opt = sane_get_option_descriptor (device, i);
+
+ if (ro || SANE_OPTION_IS_SETTABLE (opt->cap)
+ || opt->type == SANE_TYPE_GROUP)
+ print_option (device, i, opt);
+ }
+ if (num_dev_options)
+ fputc ('\n', stdout);
+}
+
+int
+main (int argc, char **argv)
+{
+ int ch, i, index, all_options_len;
+ const SANE_Device **device_list;
+ SANE_Int num_dev_options = 0;
+ const char *devname = 0;
+ const char *defdevname = 0;
+ const char *format = 0;
+ char readbuf[2];
+ char *readbuf2;
+ int batch = 0;
+ int batch_prompt = 0;
+ int batch_count = BATCH_COUNT_UNLIMITED;
+ int batch_start_at = 1;
+ int batch_increment = 1;
+ SANE_Status status;
+ char *full_optstring;
+ SANE_Int version_code;
+
+ atexit (scanimage_exit);
+
+ buffer_size = (32 * 1024); /* default size */
+
+ prog_name = strrchr (argv[0], '/');
+ if (prog_name)
+ ++prog_name;
+ else
+ prog_name = argv[0];
+
+ defdevname = getenv ("SANE_DEFAULT_DEVICE");
+
+ sane_init (&version_code, auth_callback);
+
+ /* make a first pass through the options with error printing and argument
+ permutation disabled: */
+ opterr = 0;
+ while ((ch = getopt_long (argc, argv, "-" BASE_OPTSTRING, basic_options,
+ &index)) != EOF)
+ {
+ switch (ch)
+ {
+ case ':':
+ case '?':
+ break; /* may be an option that we'll parse later on */
+ case 'd':
+ devname = optarg;
+ break;
+ case 'b':
+ /* This may have already been set by the batch-count flag */
+ batch = 1;
+ format = optarg;
+ break;
+ case 'h':
+ help = 1;
+ break;
+ case 'i': /* icc profile */
+ icc_profile = optarg;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'p':
+ progress = 1;
+ break;
+ case 'B':
+ if (optarg)
+ buffer_size = 1024 * atoi(optarg);
+ else
+ buffer_size = (1024 * 1024);
+ break;
+ case 'T':
+ test = 1;
+ break;
+ case 'A':
+ all = 1;
+ break;
+ case 'n':
+ dont_scan = 1;
+ break;
+ case OPTION_BATCH_PROMPT:
+ batch_prompt = 1;
+ break;
+ case OPTION_BATCH_INCREMENT:
+ batch_increment = atoi (optarg);
+ break;
+ case OPTION_BATCH_START_AT:
+ batch_start_at = atoi (optarg);
+ break;
+ case OPTION_BATCH_DOUBLE:
+ batch_increment = 2;
+ break;
+ case OPTION_BATCH_COUNT:
+ batch_count = atoi (optarg);
+ batch = 1;
+ break;
+ case OPTION_FORMAT:
+ if (strcmp (optarg, "tiff") == 0)
+ output_format = OUTPUT_TIFF;
+ else
+ output_format = OUTPUT_PNM;
+ break;
+ case OPTION_MD5:
+ accept_only_md5_auth = 1;
+ break;
+ case 'L':
+ case 'f':
+ {
+ int i = 0;
+
+ status = sane_get_devices (&device_list, SANE_FALSE);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_get_devices() failed: %s\n",
+ prog_name, sane_strstatus (status));
+ exit (1);
+ }
+
+ if (ch == 'L')
+ {
+ for (i = 0; device_list[i]; ++i)
+ {
+ printf ("device `%s' is a %s %s %s\n",
+ device_list[i]->name, device_list[i]->vendor,
+ device_list[i]->model, device_list[i]->type);
+ }
+ }
+ else
+ {
+ int i = 0, int_arg = 0;
+ char *percent, *start, *fmt;
+ const char *text_arg = 0;
+ char cc, ftype;
+
+ fmt = malloc (strlen (optarg) + 1);
+ if (fmt == 0)
+ {
+ fprintf (stderr, "%s: not enough memory\n", prog_name);
+ exit (1);
+ }
+
+ for (i = 0; device_list[i]; ++i)
+ {
+ strcpy (fmt, optarg);
+ start = fmt;
+ while (*start && (percent = strchr (start, '%')))
+ {
+ percent++;
+ if (*percent)
+ {
+ switch (*percent)
+ {
+ case 'd':
+ text_arg = device_list[i]->name;
+ ftype = *percent = 's';
+ break;
+ case 'v':
+ text_arg = device_list[i]->vendor;
+ ftype = *percent = 's';
+ break;
+ case 'm':
+ text_arg = device_list[i]->model;
+ ftype = *percent = 's';
+ break;
+ case 't':
+ text_arg = device_list[i]->type;
+ ftype = *percent = 's';
+ break;
+ case 'i':
+ int_arg = i;
+ ftype = 'i';
+ break;
+ case 'n':
+ text_arg = "\n";
+ ftype = *percent = 's';
+ break;
+ case '%':
+ ftype = 0;
+ break;
+ default:
+ fprintf (stderr,
+ "%s: unknown format specifier %%%c\n",
+ prog_name, *percent);
+ *percent = '%';
+ ftype = 0;
+ }
+ percent++;
+ cc = *percent;
+ *percent = 0;
+ switch (ftype)
+ {
+ case 's':
+ printf (start, text_arg);
+ break;
+ case 'i':
+ printf (start, int_arg);
+ break;
+ case 0:
+ printf (start);
+ break;
+ }
+ *percent = cc;
+ start = percent;
+ }
+ else
+ {
+ /* last char of the string is a '%', suppress it */
+ *start = 0;
+ break;
+ }
+ }
+ if (*start)
+ printf (start);
+ }
+ }
+ if (i == 0 && ch != 'f')
+ printf ("\nNo scanners were identified. If you were expecting "
+ "something different,\ncheck that the scanner is plugged "
+ "in, turned on and detected by the\nsane-find-scanner tool "
+ "(if appropriate). Please read the documentation\nwhich came "
+ "with this software (README, FAQ, manpages).\n");
+
+ if (defdevname)
+ printf ("default device is `%s'\n", defdevname);
+ exit (0);
+ }
+
+ case 'V':
+ printf ("scanimage (%s) %s; backend version %d.%d.%d\n", PACKAGE,
+ VERSION, SANE_VERSION_MAJOR (version_code),
+ SANE_VERSION_MINOR (version_code),
+ SANE_VERSION_BUILD (version_code));
+ exit (0);
+
+ default:
+ break; /* ignore device specific options for now */
+ }
+ }
+
+ if (help)
+ {
+ printf ("Usage: %s [OPTION]...\n\
+\n\
+Start image acquisition on a scanner device and write image data to\n\
+standard output.\n\
+\n\
+Parameters are separated by a blank from single-character options (e.g.\n\
+-d epson) and by a \"=\" from multi-character options (e.g. --device-name=epson).\n\
+-d, --device-name=DEVICE use a given scanner device (e.g. hp:/dev/scanner)\n\
+ --format=pnm|tiff file format of output file\n\
+-i, --icc-profile=PROFILE include this ICC profile into TIFF file\n", prog_name);
+ printf ("\
+-L, --list-devices show available scanner devices\n\
+-f, --formatted-device-list=FORMAT similar to -L, but the FORMAT of the output\n\
+ can be specified: %%d (device name), %%v (vendor),\n\
+ %%m (model), %%t (type), %%i (index number), and\n\
+ %%n (newline)\n\
+-b, --batch[=FORMAT] working in batch mode, FORMAT is `out%%d.pnm' or\n\
+ `out%%d.tif' by default depending on --format\n");
+ printf ("\
+ --batch-start=# page number to start naming files with\n\
+ --batch-count=# how many pages to scan in batch mode\n\
+ --batch-increment=# increase page number in filename by #\n\
+ --batch-double increment page number by two, same as\n\
+ --batch-increment=2\n\
+ --batch-prompt ask for pressing a key before scanning a page\n\
+ --accept-md5-only only accept authorization requests using md5\n");
+ printf ("\
+-p, --progress print progress messages\n\
+-n, --dont-scan only set options, don't actually scan\n\
+-T, --test test backend thoroughly\n\
+-A, --all-options list all available backend options\n\
+-h, --help display this help message and exit\n\
+-v, --verbose give even more status messages\n\
+-B, --buffer-size=# change input buffer size (in kB, default 32)\n\
+-V, --version print version information\n");
+ }
+
+ if (!devname)
+ {
+ /* If no device name was specified explicitly, we look at the
+ environment variable SANE_DEFAULT_DEVICE. If this variable
+ is not set, we open the first device we find (if any): */
+ devname = defdevname;
+ if (!devname)
+ {
+ status = sane_get_devices (&device_list, SANE_FALSE);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_get_devices() failed: %s\n",
+ prog_name, sane_strstatus (status));
+ exit (1);
+ }
+ if (!device_list[0])
+ {
+ fprintf (stderr, "%s: no SANE devices found\n", prog_name);
+ exit (1);
+ }
+ devname = device_list[0]->name;
+ }
+ }
+
+ status = sane_open (devname, &device);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: open of device %s failed: %s\n",
+ prog_name, devname, sane_strstatus (status));
+ if (devname[0] == '/')
+ fprintf (stderr, "\nYou seem to have specified a UNIX device name, "
+ "or filename instead of selecting\nthe SANE scanner or "
+ "image acquisition device you want to use. As an example,\n"
+ "you might want \"epson:/dev/sg0\" or "
+ "\"hp:/dev/usbscanner0\". If any supported\ndevices are "
+ "installed in your system, you should be able to see a "
+ "list with\n\"scanimage --list-devices\".\n");
+ if (help)
+ device = 0;
+ else
+ exit (1);
+ }
+
+ if (device)
+ {
+ const SANE_Option_Descriptor * desc_ptr;
+
+ /* Good form to always get the descriptor once before value */
+ desc_ptr = sane_get_option_descriptor(device, 0);
+ if (!desc_ptr)
+ {
+ fprintf (stderr, "%s: unable to get option count descriptor\n",
+ prog_name);
+ exit (1);
+ }
+
+ /* We got a device, find out how many options it has */
+ status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE,
+ &num_dev_options, 0);
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: unable to determine option count\n",
+ prog_name);
+ exit (1);
+ }
+
+ /* malloc global option lists */
+ all_options_len = num_dev_options + NELEMS (basic_options) + 1;
+ all_options = malloc (all_options_len * sizeof (all_options[0]));
+ option_number_len = num_dev_options;
+ option_number = malloc (option_number_len * sizeof (option_number[0]));
+ if (!all_options || !option_number)
+ {
+ fprintf (stderr, "%s: out of memory in main()\n",
+ prog_name);
+ exit (1);
+ }
+
+ /* load global option lists */
+ fetch_options (device);
+
+ {
+ char *larg, *targ, *xarg, *yarg;
+ larg = targ = xarg = yarg = "";
+
+ /* Maybe accept t, l, x, and y options. */
+ if (window[0])
+ xarg = "x:";
+
+ if (window[1])
+ yarg = "y:";
+
+ if (window[2])
+ larg = "l:";
+
+ if (window[3])
+ targ = "t:";
+
+ /* Now allocate the full option list. */
+ full_optstring = malloc (strlen (BASE_OPTSTRING)
+ + strlen (larg) + strlen (targ)
+ + strlen (xarg) + strlen (yarg) + 1);
+
+ if (!full_optstring)
+ {
+ fprintf (stderr, "%s: out of memory\n", prog_name);
+ exit (1);
+ }
+
+ strcpy (full_optstring, BASE_OPTSTRING);
+ strcat (full_optstring, larg);
+ strcat (full_optstring, targ);
+ strcat (full_optstring, xarg);
+ strcat (full_optstring, yarg);
+ }
+
+ /* re-run argument processing with backend-specific options included
+ * this time, enable error printing and arg permutation */
+ optind = 0;
+ opterr = 1;
+ while ((ch = getopt_long (argc, argv, full_optstring, all_options,
+ &index)) != EOF)
+ {
+ switch (ch)
+ {
+ case ':':
+ case '?':
+ exit (1); /* error message is printed by getopt_long() */
+
+ case 'd':
+ case 'h':
+ case 'p':
+ case 'v':
+ case 'V':
+ case 'T':
+ case 'B':
+ /* previously handled options */
+ break;
+
+ case 'x':
+ window_val_user[0] = 1;
+ parse_vector (&window_option[0], optarg, &window_val[0], 1);
+ break;
+
+ case 'y':
+ window_val_user[1] = 1;
+ parse_vector (&window_option[1], optarg, &window_val[1], 1);
+ break;
+
+ case 'l': /* tl-x */
+ process_backend_option (device, window[2], optarg);
+ break;
+
+ case 't': /* tl-y */
+ process_backend_option (device, window[3], optarg);
+ break;
+
+ case 0:
+ process_backend_option (device, option_number[index], optarg);
+ break;
+ }
+ }
+ if (optind < argc)
+ {
+ fprintf (stderr, "%s: argument without option: `%s'; ", prog_name,
+ argv[argc - 1]);
+ fprintf (stderr, "try %s --help\n", prog_name);
+ exit (1);
+ }
+
+ free (full_optstring);
+
+ /* convert x/y to br_x/br_y */
+ for (index = 0; index < 2; ++index)
+ if (window[index])
+ {
+ SANE_Word pos = 0;
+ SANE_Word val = window_val[index];
+
+ if (window[index + 2])
+ {
+ sane_control_option (device, window[index + 2],
+ SANE_ACTION_GET_VALUE, &pos, 0);
+ val += pos;
+ }
+ set_option (device, window[index], &val);
+ }
+
+ /* output device-specific help */
+ if (help)
+ {
+ printf ("\nOptions specific to device `%s':\n", devname);
+ print_options(device, num_dev_options, SANE_FALSE);
+ }
+
+ /* list all device-specific options */
+ if (all)
+ {
+ printf ("\nAll options specific to device `%s':\n", devname);
+ print_options(device, num_dev_options, SANE_TRUE);
+ exit (0);
+ }
+ }
+
+ /* output device list */
+ if (help)
+ {
+ printf ("\
+Type ``%s --help -d DEVICE'' to get list of all options for DEVICE.\n\
+\n\
+List of available devices:", prog_name);
+ status = sane_get_devices (&device_list, SANE_FALSE);
+ if (status == SANE_STATUS_GOOD)
+ {
+ int column = 80;
+
+ for (i = 0; device_list[i]; ++i)
+ {
+ if (column + strlen (device_list[i]->name) + 1 >= 80)
+ {
+ printf ("\n ");
+ column = 4;
+ }
+ if (column > 4)
+ {
+ fputc (' ', stdout);
+ column += 1;
+ }
+ fputs (device_list[i]->name, stdout);
+ column += strlen (device_list[i]->name);
+ }
+ }
+ fputc ('\n', stdout);
+ exit (0);
+ }
+
+ if (dont_scan)
+ exit (0);
+
+ if (output_format != OUTPUT_PNM)
+ resolution_value = get_resolution ();
+
+#ifdef SIGHUP
+ signal (SIGHUP, sighandler);
+#endif
+#ifdef SIGPIPE
+ signal (SIGPIPE, sighandler);
+#endif
+ signal (SIGINT, sighandler);
+ signal (SIGTERM, sighandler);
+
+ if (test == 0)
+ {
+ int n = batch_start_at;
+
+ if (batch && NULL == format)
+ {
+ if (output_format == OUTPUT_TIFF)
+ format = "out%d.tif";
+ else
+ format = "out%d.pnm";
+ }
+
+ if (batch)
+ fprintf (stderr,
+ "Scanning %d pages, incrementing by %d, numbering from %d\n",
+ batch_count, batch_increment, batch_start_at);
+
+ else if(isatty(fileno(stdout))){
+ fprintf (stderr,"%s: output is not a file, exiting\n", prog_name);
+ exit (1);
+ }
+
+ buffer = malloc (buffer_size);
+
+ do
+ {
+ char path[PATH_MAX];
+ char part_path[PATH_MAX];
+ if (batch) /* format is NULL unless batch mode */
+ {
+ sprintf (path, format, n); /* love --(C++) */
+ strcpy (part_path, path);
+ strcat (part_path, ".part");
+ }
+
+
+ if (batch)
+ {
+ if (batch_prompt)
+ {
+ fprintf (stderr, "Place document no. %d on the scanner.\n",
+ n);
+ fprintf (stderr, "Press <RETURN> to continue.\n");
+ fprintf (stderr, "Press Ctrl + D to terminate.\n");
+ readbuf2 = fgets (readbuf, 2, stdin);
+
+ if (readbuf2 == NULL)
+ {
+ fprintf (stderr, "Batch terminated, %d pages scanned\n",
+ (n - batch_increment));
+ fclose (stdout);
+ break; /* get out of this loop */
+ }
+ }
+ fprintf (stderr, "Scanning page %d\n", n);
+ }
+
+#ifdef SANE_STATUS_WARMING_UP
+ do
+ {
+ status = sane_start (device);
+ }
+ while(status == SANE_STATUS_WARMING_UP);
+#else
+ status = sane_start (device);
+#endif
+ if (status != SANE_STATUS_GOOD)
+ {
+ fprintf (stderr, "%s: sane_start: %s\n",
+ prog_name, sane_strstatus (status));
+ fclose (stdout);
+ break;
+ }
+
+ /* write to .part file while scanning is in progress */
+ if (batch && NULL == freopen (part_path, "w", stdout))
+ {
+ fprintf (stderr, "cannot open %s\n", part_path);
+ sane_cancel (device);
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+
+ status = scan_it ();
+ if (batch)
+ {
+ fprintf (stderr, "Scanned page %d.", n);
+ fprintf (stderr, " (scanner status = %d)\n", status);
+ }
+
+ switch (status)
+ {
+ case SANE_STATUS_GOOD:
+ case SANE_STATUS_EOF:
+ status = SANE_STATUS_GOOD;
+ if (batch)
+ {
+ /* close output file by redirecting, do not close
+ stdout here! */
+ if (NULL == freopen ("/dev/null", "w", stdout))
+ {
+ fprintf (stderr, "cannot open /dev/null\n");
+ sane_cancel (device);
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+ else
+ {
+ /* let the fully scanned file show up */
+ if (rename (part_path, path))
+ {
+ fprintf (stderr, "cannot rename %s to %s\n",
+ part_path, path);
+ sane_cancel (device);
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+ }
+ }
+ break;
+ default:
+ if (batch)
+ {
+ fclose (stdout);
+ unlink (part_path);
+ }
+ break;
+ } /* switch */
+ n += batch_increment;
+ }
+ while ((batch
+ && (batch_count == BATCH_COUNT_UNLIMITED || --batch_count))
+ && SANE_STATUS_GOOD == status);
+
+ sane_cancel (device);
+ }
+ else
+ status = test_it ();
+
+ return status;
+}
diff --git a/frontend/stiff.c b/frontend/stiff.c
new file mode 100644
index 0000000..1107e09
--- /dev/null
+++ b/frontend/stiff.c
@@ -0,0 +1,610 @@
+/* Create SANE/tiff headers TIFF interfacing routines for SANE
+ Copyright (C) 2000 Peter Kirchgessner
+ Copyright (C) 2002 Oliver Rauch: added tiff ICC profile
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Changes:
+ 2000-11-19, PK: Color TIFF-header: write 3 values for bits per sample
+ 2001-12-16, PK: Write fill order tag for b/w-images
+ 2002-08-27, OR: Added tiff tag for ICC profile
+*/
+#ifdef _AIX
+# include "../include/lalloca.h" /* MUST come first for AIX! */
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "../include/sane/config.h"
+#include "../include/sane/sane.h"
+
+#include "stiff.h"
+
+typedef struct {
+ int tag, typ, nvals, val;
+} IFD_ENTRY;
+
+
+typedef struct {
+ int maxtags;
+ int ntags;
+ IFD_ENTRY *ifde;
+} IFD;
+
+#define IFDE_TYP_BYTE (1)
+#define IFDE_TYP_ASCII (2)
+#define IFDE_TYP_SHORT (3)
+#define IFDE_TYP_LONG (4)
+#define IFDE_TYP_RATIONAL (5)
+
+static IFD *
+create_ifd (void)
+
+{ IFD *ifd;
+ int maxtags = 10;
+
+ ifd = (IFD *)malloc (sizeof (IFD));
+ if (ifd == NULL) return NULL;
+
+ ifd->ifde = (IFD_ENTRY *)malloc (maxtags * sizeof (IFD_ENTRY));
+ if (ifd->ifde == NULL)
+ {
+ free (ifd);
+ return NULL;
+ }
+ ifd->ntags = 0;
+ ifd->maxtags = maxtags;
+
+ return ifd;
+}
+
+static void
+free_ifd (IFD *ifd)
+
+{
+ if (ifd == NULL) return;
+ if (ifd->ifde != NULL)
+ {
+ free (ifd->ifde);
+ ifd->ifde = NULL;
+ }
+ free (ifd);
+ ifd = NULL;
+}
+
+static void
+add_ifd_entry (IFD *ifd, int tag, int typ, int nvals, int val)
+
+{ IFD_ENTRY *ifde;
+ int add_entries = 10;
+
+ if (ifd == NULL) return;
+ if (ifd->ntags == ifd->maxtags)
+ {
+ ifde = (IFD_ENTRY *)realloc (ifd->ifde,
+ (ifd->maxtags+add_entries)*sizeof (IFD_ENTRY));
+ if (ifde == NULL) return;
+ ifd->ifde = ifde;
+ ifd->maxtags += add_entries;
+ }
+ ifde = &(ifd->ifde[ifd->ntags]);
+ ifde->tag = tag;
+ ifde->typ = typ;
+ ifde->nvals = nvals;
+ ifde->val = val;
+ (ifd->ntags)++;
+}
+
+static void
+write_i2 (FILE *fptr, int val, int motorola)
+{
+ if (motorola)
+ {
+ putc ((val >> 8) & 0xff, fptr);
+ putc (val & 0xff, fptr);
+ }
+ else
+ {
+ putc (val & 0xff, fptr);
+ putc ((val >> 8) & 0xff, fptr);
+ }
+}
+
+
+static void
+write_i4 (FILE *fptr, int val, int motorola)
+{
+ if (motorola)
+ {
+ putc ((val >> 24) & 0xff, fptr);
+ putc ((val >> 16) & 0xff, fptr);
+ putc ((val >> 8) & 0xff, fptr);
+ putc (val & 0xff, fptr);
+ }
+ else
+ {
+ putc (val & 0xff, fptr);
+ putc ((val >> 8) & 0xff, fptr);
+ putc ((val >> 16) & 0xff, fptr);
+ putc ((val >> 24) & 0xff, fptr);
+ }
+}
+
+static void
+write_ifd (FILE *fptr, IFD *ifd, int motorola)
+{int k;
+ IFD_ENTRY *ifde;
+
+ if (!ifd) return;
+
+ if (motorola) putc ('M', fptr), putc ('M', fptr);
+ else putc ('I', fptr), putc ('I', fptr);
+
+ write_i2 (fptr, 42, motorola); /* Magic */
+ write_i4 (fptr, 8, motorola); /* Offset to first IFD */
+ write_i2 (fptr, ifd->ntags, motorola);
+
+ for (k = 0; k < ifd->ntags; k++)
+ {
+ ifde = &(ifd->ifde[k]);
+ write_i2 (fptr, ifde->tag, motorola);
+ write_i2 (fptr, ifde->typ, motorola);
+ write_i4 (fptr, ifde->nvals, motorola);
+ if ((ifde->typ == IFDE_TYP_SHORT) && (ifde->nvals == 1))
+ {
+ write_i2 (fptr, ifde->val, motorola);
+ write_i2 (fptr, 0, motorola);
+ }
+ else
+ {
+ write_i4 (fptr, ifde->val, motorola);
+ }
+ }
+ write_i4 (fptr, 0, motorola); /* End of IFD chain */
+}
+
+
+static void
+write_tiff_bw_header (FILE *fptr, int width, int height, int resolution)
+{IFD *ifd;
+ int header_size = 8, ifd_size;
+ int strip_offset, data_offset, data_size;
+ int strip_bytecount;
+ int ntags;
+ int motorola;
+
+ ifd = create_ifd ();
+
+ strip_bytecount = ((width+7)/8) * height;
+
+ /* the following values must be known in advance */
+ ntags = 12;
+ data_size = 0;
+ if (resolution > 0)
+ {
+ ntags += 3;
+ data_size += 2*4 + 2*4;
+ }
+
+ ifd_size = 2 + ntags*12 + 4;
+ data_offset = header_size + ifd_size;
+ strip_offset = data_offset + data_size;
+
+ /* New subfile type */
+ add_ifd_entry (ifd, 254, IFDE_TYP_LONG, 1, 0);
+ /* image width */
+ add_ifd_entry (ifd, 256, (width > 0xffff) ? IFDE_TYP_LONG : IFDE_TYP_SHORT,
+ 1, width);
+ /* image length */
+ add_ifd_entry (ifd, 257, (height > 0xffff) ? IFDE_TYP_LONG : IFDE_TYP_SHORT,
+ 1, height);
+ /* bits per sample */
+ add_ifd_entry (ifd, 258, IFDE_TYP_SHORT, 1, 1);
+ /* compression (uncompressed) */
+ add_ifd_entry (ifd, 259, IFDE_TYP_SHORT, 1, 1);
+ /* photometric interpretation */
+ add_ifd_entry (ifd, 262, IFDE_TYP_SHORT, 1, 0);
+ /* fill order */
+ add_ifd_entry (ifd, 266, IFDE_TYP_SHORT, 1, 1);
+ /* strip offset */
+ add_ifd_entry (ifd, 273, IFDE_TYP_LONG, 1, strip_offset);
+ /* orientation */
+ add_ifd_entry (ifd, 274, IFDE_TYP_SHORT, 1, 1);
+ /* samples per pixel */
+ add_ifd_entry (ifd, 277, IFDE_TYP_SHORT, 1, 1);
+ /* rows per strip */
+ add_ifd_entry (ifd, 278, IFDE_TYP_LONG, 1, height);
+ /* strip bytecount */
+ add_ifd_entry (ifd, 279, IFDE_TYP_LONG, 1, strip_bytecount);
+ if (resolution > 0)
+ {
+ /* x resolution */
+ add_ifd_entry (ifd, 282, IFDE_TYP_RATIONAL, 1, data_offset);
+ data_offset += 2*4;
+ /* y resolution */
+ add_ifd_entry (ifd, 283, IFDE_TYP_RATIONAL, 1, data_offset);
+ data_offset += 2*4;
+ }
+ if (resolution > 0)
+ {
+ /* resolution unit (dpi) */
+ add_ifd_entry (ifd, 296, IFDE_TYP_SHORT, 1, 2);
+ }
+
+ /* I prefer motorola format. Its human readable. */
+ motorola = 1;
+ write_ifd (fptr, ifd, motorola);
+
+ /* Write x/y resolution */
+ if (resolution > 0)
+ {
+ write_i4 (fptr, resolution, motorola);
+ write_i4 (fptr, 1, motorola);
+ write_i4 (fptr, resolution, motorola);
+ write_i4 (fptr, 1, motorola);
+ }
+
+ free_ifd (ifd);
+}
+
+static void
+write_tiff_grey_header (FILE *fptr, int width, int height, int depth,
+ int resolution, const char *icc_profile)
+{IFD *ifd;
+ int header_size = 8, ifd_size;
+ int strip_offset, data_offset, data_size;
+ int strip_bytecount;
+ int ntags;
+ int motorola, bps, maxsamplevalue;
+ FILE *icc_file = 0;
+ int icc_len = -1;
+
+ if (icc_profile)
+ {
+ icc_file = fopen(icc_profile, "r");
+
+ if (!icc_file)
+ {
+ fprintf(stderr, "Could not open ICC profile %s\n", icc_profile);
+ }
+ else
+ {
+ icc_len = 16777216 * fgetc(icc_file) + 65536 * fgetc(icc_file) + 256 * fgetc(icc_file) + fgetc(icc_file);
+ rewind(icc_file);
+ }
+ }
+
+ ifd = create_ifd ();
+
+ bps = (depth <= 8) ? 1 : 2; /* Bytes per sample */
+ maxsamplevalue = (depth <= 8) ? 255 : 65535;
+ strip_bytecount = width * height * bps;
+
+ /* the following values must be known in advance */
+ ntags = 13;
+ data_size = 0;
+ if (resolution > 0)
+ {
+ ntags += 3;
+ data_size += 2*4 + 2*4;
+ }
+
+ if (icc_len > 0) /* if icc profile exists add memory for tag */
+ {
+ ntags += 1;
+ data_size += icc_len;
+ }
+
+ ifd_size = 2 + ntags*12 + 4;
+ data_offset = header_size + ifd_size;
+ strip_offset = data_offset + data_size;
+
+ /* New subfile type */
+ add_ifd_entry (ifd, 254, IFDE_TYP_LONG, 1, 0);
+ /* image width */
+ add_ifd_entry (ifd, 256, (width > 0xffff) ? IFDE_TYP_LONG : IFDE_TYP_SHORT,
+ 1, width);
+ /* image length */
+ add_ifd_entry (ifd, 257, (height > 0xffff) ? IFDE_TYP_LONG : IFDE_TYP_SHORT,
+ 1, height);
+ /* bits per sample */
+ add_ifd_entry (ifd, 258, IFDE_TYP_SHORT, 1, depth);
+ /* compression (uncompressed) */
+ add_ifd_entry (ifd, 259, IFDE_TYP_SHORT, 1, 1);
+ /* photometric interpretation */
+ add_ifd_entry (ifd, 262, IFDE_TYP_SHORT, 1, 1);
+ /* strip offset */
+ add_ifd_entry (ifd, 273, IFDE_TYP_LONG, 1, strip_offset);
+ /* orientation */
+ add_ifd_entry (ifd, 274, IFDE_TYP_SHORT, 1, 1);
+ /* samples per pixel */
+ add_ifd_entry (ifd, 277, IFDE_TYP_SHORT, 1, 1);
+ /* rows per strip */
+ add_ifd_entry (ifd, 278, IFDE_TYP_LONG, 1, height);
+ /* strip bytecount */
+ add_ifd_entry (ifd, 279, IFDE_TYP_LONG, 1, strip_bytecount);
+ /* min sample value */
+ add_ifd_entry (ifd, 280, IFDE_TYP_SHORT, 1, 0);
+ /* max sample value */
+ add_ifd_entry (ifd, 281, IFDE_TYP_SHORT, 1, maxsamplevalue);
+ if (resolution > 0)
+ {
+ /* x resolution */
+ add_ifd_entry (ifd, 282, IFDE_TYP_RATIONAL, 1, data_offset);
+ data_offset += 2*4;
+ /* y resolution */
+ add_ifd_entry (ifd, 283, IFDE_TYP_RATIONAL, 1, data_offset);
+ data_offset += 2*4;
+ }
+ if (resolution > 0)
+ {
+ /* resolution unit (dpi) */
+ add_ifd_entry (ifd, 296, IFDE_TYP_SHORT, 1, 2);
+ }
+
+ if (icc_len > 0) /* add ICC-profile TAG */
+ {
+ add_ifd_entry(ifd, 34675, 7, icc_len, data_offset);
+ data_offset += icc_len;
+ }
+
+ /* I prefer motorola format. Its human readable. But for 16 bit, */
+ /* the image format is defined by SANE to be the native byte order */
+ if (bps == 1)
+ {
+ motorola = 1;
+ }
+ else
+ {int check = 1;
+ motorola = ((*((char *)&check)) == 0);
+ }
+
+ write_ifd (fptr, ifd, motorola);
+
+ /* Write x/y resolution */
+ if (resolution > 0)
+ {
+ write_i4 (fptr, resolution, motorola);
+ write_i4 (fptr, 1, motorola);
+ write_i4 (fptr, resolution, motorola);
+ write_i4 (fptr, 1, motorola);
+ }
+
+ /* Write ICC profile */
+ if (icc_len > 0)
+ {
+ int i;
+ for (i=0; i<icc_len; i++)
+ {
+ if (!feof(icc_file))
+ {
+ fputc(fgetc(icc_file), fptr);
+ }
+ else
+ {
+ fprintf(stderr, "ICC profile %s is too short\n", icc_profile);
+ break;
+ }
+ }
+ }
+
+ if (icc_file)
+ {
+ fclose(icc_file);
+ }
+
+ free_ifd (ifd);
+}
+
+
+static void
+write_tiff_color_header (FILE *fptr, int width, int height, int depth,
+ int resolution, const char *icc_profile)
+{IFD *ifd;
+ int header_size = 8, ifd_size;
+ int strip_offset, data_offset, data_size;
+ int strip_bytecount;
+ int ntags;
+ int motorola, bps, maxsamplevalue;
+ FILE *icc_file = 0;
+ int icc_len = -1;
+
+ if (icc_profile)
+ {
+ icc_file = fopen(icc_profile, "r");
+
+ if (!icc_file)
+ {
+ fprintf(stderr, "Could not open ICC profile %s\n", icc_profile);
+ }
+ else
+ {
+ icc_len = 16777216 * fgetc(icc_file) + 65536 * fgetc(icc_file) + 256 * fgetc(icc_file) + fgetc(icc_file);
+ rewind(icc_file);
+ }
+ }
+
+
+ ifd = create_ifd ();
+
+ bps = (depth <= 8) ? 1 : 2; /* Bytes per sample */
+ maxsamplevalue = (depth <= 8) ? 255 : 65535;
+ strip_bytecount = width * height * 3 * bps;
+
+ /* the following values must be known in advance */
+ ntags = 13;
+ data_size = 3*2 + 3*2 + 3*2;
+
+ if (resolution > 0)
+ {
+ ntags += 3;
+ data_size += 2*4 + 2*4;
+ }
+
+ if (icc_len > 0) /* if icc profile exists add memory for tag */
+ {
+ ntags += 1;
+ data_size += icc_len;
+ }
+
+
+ ifd_size = 2 + ntags*12 + 4;
+ data_offset = header_size + ifd_size;
+ strip_offset = data_offset + data_size;
+
+ /* New subfile type */
+ add_ifd_entry (ifd, 254, IFDE_TYP_LONG, 1, 0);
+ /* image width */
+ add_ifd_entry (ifd, 256, (width > 0xffff) ? IFDE_TYP_LONG : IFDE_TYP_SHORT,
+ 1, width);
+ /* image length */
+ add_ifd_entry (ifd, 257, (height > 0xffff) ? IFDE_TYP_LONG : IFDE_TYP_SHORT,
+ 1, height);
+ /* bits per sample */
+ add_ifd_entry (ifd, 258, IFDE_TYP_SHORT, 3, data_offset);
+ data_offset += 3*2;
+ /* compression (uncompressed) */
+ add_ifd_entry (ifd, 259, IFDE_TYP_SHORT, 1, 1);
+ /* photometric interpretation */
+ add_ifd_entry (ifd, 262, IFDE_TYP_SHORT, 1, 2);
+ /* strip offset */
+ add_ifd_entry (ifd, 273, IFDE_TYP_LONG, 1, strip_offset);
+ /* orientation */
+ add_ifd_entry (ifd, 274, IFDE_TYP_SHORT, 1, 1);
+ /* samples per pixel */
+ add_ifd_entry (ifd, 277, IFDE_TYP_SHORT, 1, 3);
+ /* rows per strip */
+ add_ifd_entry (ifd, 278, IFDE_TYP_LONG, 1, height);
+ /* strip bytecount */
+ add_ifd_entry (ifd, 279, IFDE_TYP_LONG, 1, strip_bytecount);
+ /* min sample value */
+ add_ifd_entry (ifd, 280, IFDE_TYP_SHORT, 3, data_offset);
+ data_offset += 3*2;
+ /* max sample value */
+ add_ifd_entry (ifd, 281, IFDE_TYP_SHORT, 3, data_offset);
+ data_offset += 3*2;
+
+ if (resolution > 0)
+ {
+ /* x resolution */
+ add_ifd_entry (ifd, 282, IFDE_TYP_RATIONAL, 1, data_offset);
+ data_offset += 2*4;
+ /* y resolution */
+ add_ifd_entry (ifd, 283, IFDE_TYP_RATIONAL, 1, data_offset);
+ data_offset += 2*4;
+ }
+
+ if (resolution > 0)
+ {
+ /* resolution unit (dpi) */
+ add_ifd_entry (ifd, 296, IFDE_TYP_SHORT, 1, 2);
+ }
+
+ if (icc_len > 0) /* add ICC-profile TAG */
+ {
+ add_ifd_entry(ifd, 34675, 7, icc_len, data_offset);
+ data_offset += icc_len;
+ }
+
+
+ /* I prefer motorola format. Its human readable. But for 16 bit, */
+ /* the image format is defined by SANE to be the native byte order */
+ if (bps == 1)
+ {
+ motorola = 1;
+ }
+ else
+ {int check = 1;
+ motorola = ((*((char *)&check)) == 0);
+ }
+
+ write_ifd (fptr, ifd, motorola);
+
+ /* Write bits per sample value values */
+ write_i2 (fptr, depth, motorola);
+ write_i2 (fptr, depth, motorola);
+ write_i2 (fptr, depth, motorola);
+
+ /* Write min sample value values */
+ write_i2 (fptr, 0, motorola);
+ write_i2 (fptr, 0, motorola);
+ write_i2 (fptr, 0, motorola);
+
+ /* Write max sample value values */
+ write_i2 (fptr, maxsamplevalue, motorola);
+ write_i2 (fptr, maxsamplevalue, motorola);
+ write_i2 (fptr, maxsamplevalue, motorola);
+
+ /* Write x/y resolution */
+ if (resolution > 0)
+ {
+ write_i4 (fptr, resolution, motorola);
+ write_i4 (fptr, 1, motorola);
+ write_i4 (fptr, resolution, motorola);
+ write_i4 (fptr, 1, motorola);
+ }
+
+ /* Write ICC profile */
+ if (icc_len > 0)
+ {
+ int i;
+ for (i=0; i<icc_len; i++)
+ {
+ if (!feof(icc_file))
+ {
+ fputc(fgetc(icc_file), fptr);
+ }
+ else
+ {
+ fprintf(stderr, "ICC profile %s is too short\n", icc_profile);
+ break;
+ }
+ }
+ }
+
+ if (icc_file)
+ {
+ fclose(icc_file);
+ }
+
+ free_ifd (ifd);
+}
+
+
+void
+sanei_write_tiff_header (SANE_Frame format, int width, int height, int depth,
+ int resolution, const char *icc_profile)
+{
+#ifdef __EMX__ /* OS2 - write in binary mode. */
+ _fsetmode(stdout, "b");
+#endif
+ switch (format)
+ {
+ case SANE_FRAME_RED:
+ case SANE_FRAME_GREEN:
+ case SANE_FRAME_BLUE:
+ case SANE_FRAME_RGB:
+ write_tiff_color_header (stdout, width, height, depth, resolution, icc_profile);
+ break;
+
+ default:
+ if (depth == 1)
+ write_tiff_bw_header (stdout, width, height, resolution);
+ else
+ write_tiff_grey_header (stdout, width, height, depth, resolution, icc_profile);
+ break;
+ }
+}
diff --git a/frontend/stiff.h b/frontend/stiff.h
new file mode 100644
index 0000000..8583832
--- /dev/null
+++ b/frontend/stiff.h
@@ -0,0 +1,20 @@
+/* Create SANE/tiff headers TIFF interfacing routines for SANE
+ Copyright (C) 2000 Peter Kirchgessner
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+void
+sanei_write_tiff_header (SANE_Frame format, int width, int height, int depth,
+ int resolution, const char *icc_profile);
diff --git a/frontend/test.c b/frontend/test.c
new file mode 100644
index 0000000..df06751
--- /dev/null
+++ b/frontend/test.c
@@ -0,0 +1,182 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 Andreas Beck
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ This file implements a simple SANE frontend (well it rather is a
+ transport layer, but seen from libsane it is a frontend) which acts
+ as a NETSANE server. The NETSANE specifications should have come
+ with this package.
+ Feel free to enhance this program ! It needs extension especially
+ regarding crypto-support and authentication.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include "../include/sane/sane.h"
+
+void
+auth_callback (SANE_String_Const domain,
+ SANE_Char *username,
+ SANE_Char *password)
+{
+ printf ("Client '%s' requested authorization.\nUser:\n", domain);
+ scanf ("%s", username);
+ printf ("Password:\n");
+ scanf ("%s", password);
+ return;
+}
+
+void
+testsane (const char *dev_name)
+{
+ int hlp, x;
+ SANE_Status bla;
+ SANE_Int blubb;
+ SANE_Handle hand;
+ SANE_Parameters pars;
+ const SANE_Option_Descriptor *sod;
+ const SANE_Device **device_list;
+ char buffer[2048];
+
+ bla = sane_init (&blubb, auth_callback);
+ fprintf (stderr, "Init : stat=%d ver=%x\nPress Enter to continue...",
+ bla, blubb);
+ getchar ();
+ if (bla != SANE_STATUS_GOOD)
+ return;
+
+ bla = sane_get_devices (&device_list, SANE_FALSE);
+ fprintf (stderr, "GetDev : stat=%s\n", sane_strstatus (bla));
+ if (bla != SANE_STATUS_GOOD)
+ return;
+
+ bla = sane_open (dev_name, &hand);
+ fprintf (stderr, "Open : stat=%s hand=%p\n", sane_strstatus (bla), hand);
+ if (bla != SANE_STATUS_GOOD)
+ return;
+
+ bla = sane_set_io_mode (hand, 0);
+ fprintf (stderr, "SetIoMode : stat=%s\n", sane_strstatus (bla));
+
+ for (hlp = 0; hlp < 9999; hlp++)
+ {
+ sod = sane_get_option_descriptor (hand, hlp);
+ if (sod == NULL)
+ break;
+ fprintf (stderr, "Gopt(%d) : stat=%p\n", hlp, sod);
+ fprintf (stderr, "name : %s\n", sod->name);
+ fprintf (stderr, "title: %s\n", sod->title);
+ fprintf (stderr, "desc : %s\n", sod->desc);
+
+ fprintf (stderr, "type : %d\n", sod->type);
+ fprintf (stderr, "unit : %d\n", sod->unit);
+ fprintf (stderr, "size : %d\n", sod->size);
+ fprintf (stderr, "cap : %d\n", sod->cap);
+ fprintf (stderr, "ctyp : %d\n", sod->constraint_type);
+ switch (sod->constraint_type)
+ {
+ case SANE_CONSTRAINT_NONE:
+ break;
+ case SANE_CONSTRAINT_STRING_LIST:
+ fprintf (stderr, "stringlist:\n");
+ break;
+ case SANE_CONSTRAINT_WORD_LIST:
+ fprintf (stderr, "wordlist (%d) : ", sod->constraint.word_list[0]);
+ for (x = 1; x <= sod->constraint.word_list[0]; x++)
+ fprintf (stderr, " %d ", sod->constraint.word_list[x]);
+ fprintf (stderr, "\n");
+ break;
+ case SANE_CONSTRAINT_RANGE:
+ fprintf (stderr, "range: %d-%d %d \n", sod->constraint.range->min,
+ sod->constraint.range->max, sod->constraint.range->quant);
+ break;
+ }
+ }
+
+ bla = sane_get_parameters (hand, &pars);
+ fprintf (stderr,
+ "Parm : stat=%s form=%d,lf=%d,bpl=%d,pixpl=%d,lin=%d,dep=%d\n",
+ sane_strstatus (bla),
+ pars.format, pars.last_frame,
+ pars.bytes_per_line, pars.pixels_per_line,
+ pars.lines, pars.depth);
+ if (bla != SANE_STATUS_GOOD)
+ return;
+
+ bla = sane_start (hand);
+ fprintf (stderr, "Start : stat=%s\n", sane_strstatus (bla));
+ if (bla != SANE_STATUS_GOOD)
+ return;
+
+ do
+ {
+ bla = sane_read (hand, buffer, sizeof (buffer), &blubb);
+ /*printf("Read : stat=%s len=%d\n",sane_strstatus (bla),blubb); */
+ if (bla != SANE_STATUS_GOOD)
+ {
+ if (bla == SANE_STATUS_EOF)
+ break;
+ return;
+ }
+ fwrite (buffer, 1, blubb, stdout);
+ }
+ while (1);
+
+ sane_cancel (hand);
+ fprintf (stderr, "Cancel.\n");
+
+ sane_close (hand);
+ fprintf (stderr, "Close\n");
+
+ for (hlp = 0; hlp < 20; hlp++)
+ fprintf (stderr, "STRS %d=%s\n", hlp, sane_strstatus (hlp));
+
+ fprintf (stderr, "Exit.\n");
+}
+
+int
+main (int argc, char *argv[])
+{
+ if (argc != 2 && argc != 3)
+ {
+ fprintf (stderr, "Usage: %s devicename [hostname]\n", argv[0]);
+ exit (0);
+ }
+ if (argc == 3)
+ {
+ char envbuf[1024];
+ sprintf (envbuf, "SANE_NET_HOST=%s", argv[2]);
+ putenv (envbuf);
+ }
+
+ fprintf (stderr, "This is a SANE test application.\n"
+ "Now connecting to device %s.\n", argv[1]);
+ testsane (argv[1]);
+ sane_exit ();
+ return 0;
+}
diff --git a/frontend/tstbackend.c b/frontend/tstbackend.c
new file mode 100644
index 0000000..6dcd940
--- /dev/null
+++ b/frontend/tstbackend.c
@@ -0,0 +1,1871 @@
+/*
+ tstbackend -- backend test utility
+
+ Uses the SANE library.
+ Copyright (C) 2002 Frank Zago (sane at zago dot net)
+ Copyright (C) 2013 Stéphane Voltz <stef.dev@free.fr> : sane_get_devices test
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+*/
+
+#define BUILD 19 /* 2013-03-29 */
+
+#include "../include/sane/config.h"
+
+#include <assert.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/saneopts.h"
+
+static struct option basic_options[] = {
+ {"device-name", required_argument, NULL, 'd'},
+ {"level", required_argument, NULL, 'l'},
+ {"recursion", required_argument, NULL, 'r'},
+ {"get-devices", required_argument, NULL, 'g'},
+ {"help", 0, NULL, 'h'}
+};
+
+static void
+test_options (SANE_Device * device, int can_do_recursive);
+
+enum message_level {
+ MSG, /* info message */
+ INF, /* non-urgent warning */
+ WRN, /* warning */
+ ERR, /* error, test can continue */
+ FATAL, /* error, test can't/mustn't continue */
+ BUG /* bug in tstbackend */
+};
+
+int message_number_wrn = 0;
+int message_number_err = 0;
+#ifdef HAVE_LONG_LONG
+long long checks_done = 0;
+#else
+/* It may overflow, but it's no big deal. */
+long int checks_done = 0;
+#endif
+
+int test_level;
+int verbose_level;
+
+/* Maybe add that to sane.h */
+#define SANE_OPTION_IS_GETTABLE(cap) (((cap) & (SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE)) == SANE_CAP_SOFT_DETECT)
+
+/*--------------------------------------------------------------------------*/
+
+/* Display the message error statistics. */
+static void display_stats(void)
+{
+#ifdef HAVE_LONG_LONG
+ printf("warnings: %d error: %d checks: %lld\n",
+ message_number_wrn, message_number_err, checks_done);
+#else
+ printf("warnings: %d error: %d checks: %ld\n",
+ message_number_wrn, message_number_err, checks_done);
+#endif
+}
+
+/*
+ * If the condition is false, display a message with some headers
+ * depending on the level.
+ *
+ * Returns the condition.
+ *
+ */
+#ifdef __GNUC__
+static int check(enum message_level, int condition, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
+#endif
+static int check(enum message_level level, int condition, const char *format, ...)
+{
+ char str[1000];
+ va_list args;
+
+ if (level != MSG && level != INF) checks_done ++;
+
+ if (condition != 0)
+ return condition;
+
+ va_start(args, format);
+ vsprintf(str, format, args);
+ va_end(args);
+
+ switch(level) {
+ case MSG:
+ printf(" %s\n", str);
+ break;
+ case INF: /* info */
+ printf("info : %s\n", str);
+ break;
+ case WRN: /* warning */
+ printf("warning : %s\n", str);
+ message_number_wrn ++;
+ break;
+ case ERR: /* error */
+ printf("ERROR : %s\n", str);
+ message_number_err ++;
+ break;
+ case FATAL: /* fatal error */
+ printf("FATAL ERROR : %s\n", str);
+ message_number_err ++;
+ break;
+ case BUG: /* bug in tstbackend */
+ printf("tstbackend BUG : %s\n", str);
+ break;
+ }
+
+ if (level == FATAL || level == BUG) {
+ /* Fatal error. Generate a core dump. */
+ display_stats();
+ abort();
+ }
+
+ fflush(stdout);
+
+ return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+
+#define GUARDS_SIZE 4 /* 4 bytes */
+#define GUARD1 ((SANE_Word)0x5abf8ea5)
+#define GUARD2 ((SANE_Word)0xa58ebf5a)
+
+/* Allocate the requested memory plus enough room to store some guard bytes. */
+static void *guards_malloc(size_t size)
+{
+ unsigned char *ptr;
+
+ size += 2*GUARDS_SIZE;
+ ptr = malloc(size);
+
+ assert(ptr);
+
+ ptr += GUARDS_SIZE;
+
+ return(ptr);
+}
+
+/* Free some memory allocated by guards_malloc. */
+static void guards_free(void *ptr)
+{
+ unsigned char *p = ptr;
+
+ p -= GUARDS_SIZE;
+ free(p);
+}
+
+/* Set the guards */
+static void guards_set(void *ptr, size_t size)
+{
+ SANE_Word *p;
+
+ p = (SANE_Word *)(((unsigned char *)ptr) - GUARDS_SIZE);
+ *p = GUARD1;
+
+ p = (SANE_Word *)(((unsigned char *)ptr) + size);
+ *p = GUARD2;
+}
+
+/* Check that the guards have not been tampered with. */
+static void guards_check(void *ptr, size_t size)
+{
+ SANE_Word *p;
+
+ p = (SANE_Word *)(((unsigned char *)ptr) - GUARDS_SIZE);
+ check(FATAL, (*p == GUARD1),
+ "guard before the block has been tampered");
+
+ p = (SANE_Word *)(((unsigned char *)ptr) + size);
+ check(FATAL, (*p == GUARD2),
+ "guard after the block has been tampered");
+}
+
+/*--------------------------------------------------------------------------*/
+
+static void
+test_parameters (SANE_Device * device, SANE_Parameters *params)
+{
+ SANE_Status status;
+ SANE_Parameters p;
+
+ status = sane_get_parameters (device, &p);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot get the parameters (error %s)", sane_strstatus(status));
+
+ check(FATAL, ((p.format == SANE_FRAME_GRAY) ||
+ (p.format == SANE_FRAME_RGB) ||
+ (p.format == SANE_FRAME_RED) ||
+ (p.format == SANE_FRAME_GREEN) ||
+ (p.format == SANE_FRAME_BLUE)),
+ "parameter format is not a known SANE_FRAME_* (%d)", p.format);
+
+ check(FATAL, ((p.last_frame == SANE_FALSE) ||
+ (p.last_frame == SANE_TRUE)),
+ "parameter last_frame is neither SANE_FALSE or SANE_TRUE (%d)", p.last_frame);
+
+ check(FATAL, ((p.depth == 1) ||
+ (p.depth == 8) ||
+ (p.depth == 16)),
+ "parameter depth is neither 1, 8 or 16 (%d)", p.depth);
+
+ if (params) {
+ *params = p;
+ }
+}
+
+/* Try to set every option in a word list. */
+static void
+test_options_word_list (SANE_Device * device, int option_num,
+ const SANE_Option_Descriptor *opt,
+ int can_do_recursive)
+{
+ SANE_Status status;
+ int i;
+ SANE_Int val_int;
+ SANE_Int info;
+
+ check(FATAL, (opt->type == SANE_TYPE_INT ||
+ opt->type == SANE_TYPE_FIXED),
+ "type must be SANE_TYPE_INT or SANE_TYPE_FIXED (%d)", opt->type);
+
+ if (!SANE_OPTION_IS_SETTABLE(opt->cap)) return;
+
+ for (i=1; i<opt->constraint.word_list[0]; i++) {
+
+ info = 0x1010; /* garbage */
+
+ val_int = opt->constraint.word_list[i];
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, &info);
+
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot set a settable option (status=%s)", sane_strstatus(status));
+
+ check(WRN, ((info & ~(SANE_INFO_RELOAD_OPTIONS |
+ SANE_INFO_RELOAD_PARAMS)) == 0),
+ "sane_control_option set an invalid info (%d)", info);
+
+ if ((info & SANE_INFO_RELOAD_OPTIONS) && can_do_recursive) {
+ test_options(device, can_do_recursive-1);
+ }
+ if (info & SANE_INFO_RELOAD_PARAMS) {
+ test_parameters(device, NULL);
+ }
+
+ /* The option might have become inactive or unsettable. Skip it. */
+ if (!SANE_OPTION_IS_ACTIVE(opt->cap) ||
+ !SANE_OPTION_IS_SETTABLE(opt->cap))
+ return;
+
+ }
+}
+
+/* Try to set every option in a string list. */
+static void
+test_options_string_list (SANE_Device * device, int option_num,
+ const SANE_Option_Descriptor *opt,
+ int can_do_recursive)
+{
+ SANE_Int info;
+ SANE_Status status;
+ SANE_String val_string;
+ int i;
+
+ check(FATAL, (opt->type == SANE_TYPE_STRING),
+ "type must be SANE_TYPE_STRING (%d)", opt->type);
+
+ if (!SANE_OPTION_IS_SETTABLE(opt->cap)) return;
+
+ for (i=0; opt->constraint.string_list[i] != NULL; i++) {
+
+ val_string = strdup(opt->constraint.string_list[i]);
+ assert(val_string);
+
+ check(WRN, (strlen(val_string) < (size_t)opt->size),
+ "string [%s] is longer than the max size (%d)",
+ val_string, opt->size);
+
+ info = 0xE1000; /* garbage */
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, val_string, &info);
+
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot set a settable option (status=%s)", sane_strstatus(status));
+
+ check(WRN, ((info & ~(SANE_INFO_RELOAD_OPTIONS |
+ SANE_INFO_RELOAD_PARAMS)) == 0),
+ "sane_control_option set an invalid info (%d)", info);
+
+ free(val_string);
+
+ if ((info & SANE_INFO_RELOAD_OPTIONS) && can_do_recursive) {
+ test_options(device, can_do_recursive-1);
+ }
+ if (info & SANE_INFO_RELOAD_PARAMS) {
+ test_parameters(device, NULL);
+ }
+
+ /* The option might have become inactive or unsettable. Skip it. */
+ if (!SANE_OPTION_IS_ACTIVE(opt->cap) ||
+ !SANE_OPTION_IS_SETTABLE(opt->cap))
+ return;
+ }
+}
+
+/* Test the consistency of the options. */
+static void
+test_options (SANE_Device * device, int can_do_recursive)
+{
+ SANE_Word info;
+ SANE_Int num_dev_options;
+ SANE_Status status;
+ const SANE_Option_Descriptor *opt;
+ int option_num;
+ void *optval; /* value for the option */
+ size_t optsize; /* size of the optval buffer */
+
+ /*
+ * Test option 0
+ */
+ opt = sane_get_option_descriptor (device, 0);
+ check(FATAL, (opt != NULL),
+ "cannot get option descriptor for option 0 (it must exist)");
+ check(INF, (opt->cap == SANE_CAP_SOFT_DETECT),
+ "invalid capabilities for option 0 (%d)", opt->cap);
+ check(ERR, (opt->type == SANE_TYPE_INT),
+ "option 0 type must be SANE_TYPE_INT");
+
+ /* Get the number of options. */
+ status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE, &num_dev_options, 0);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot get option 0 value");
+
+ /* Try to change the number of options. */
+ status = sane_control_option (device, 0, SANE_ACTION_SET_VALUE,
+ &num_dev_options, &info);
+ check(WRN, (status != SANE_STATUS_GOOD),
+ "the option 0 value can be set");
+
+ /*
+ * Test all options
+ */
+ option_num = 0;
+ for (option_num = 0; option_num < num_dev_options; option_num++) {
+
+ /* Get the option descriptor */
+ opt = sane_get_option_descriptor (device, option_num);
+ check(FATAL, (opt != NULL),
+ "cannot get option descriptor for option %d", option_num);
+ check(WRN, ((opt->cap & ~(SANE_CAP_SOFT_SELECT |
+ SANE_CAP_HARD_SELECT |
+ SANE_CAP_SOFT_DETECT |
+ SANE_CAP_EMULATED |
+ SANE_CAP_AUTOMATIC |
+ SANE_CAP_INACTIVE |
+ SANE_CAP_ADVANCED)) == 0),
+ "invalid capabilities for option [%d, %s] (%x)", option_num, opt->name, opt->cap);
+ check(WRN, (opt->title != NULL),
+ "option [%d, %s] must have a title", option_num, opt->name);
+ check(WRN, (opt->desc != NULL),
+ "option [%d, %s] must have a description", option_num, opt->name);
+
+ if (!SANE_OPTION_IS_ACTIVE (opt->cap)) {
+ /* Option not active. Skip the remaining tests. */
+ continue;
+ }
+
+ if(verbose_level) {
+ printf("checking option ""%s""\n",opt->title);
+ }
+
+ if (opt->type == SANE_TYPE_GROUP) {
+ check(INF, (opt->name == NULL || *opt->name == 0),
+ "option [%d, %s] has a name", option_num, opt->name);
+ check(ERR, (!SANE_OPTION_IS_SETTABLE (opt->cap)),
+ "option [%d, %s], group option is settable", option_num, opt->name);
+ } else {
+ if (option_num == 0) {
+ check(ERR, (opt->name != NULL && *opt->name ==0),
+ "option 0 must have an empty name (ie. \"\")");
+ } else {
+ check(ERR, (opt->name != NULL && *opt->name !=0),
+ "option %d must have a name", option_num);
+ }
+ }
+
+ /* The option name must contain only "a".."z",
+ "0".."9" and "-" and must start with "a".."z". */
+ if (opt->name && opt->name[0]) {
+ const char *p = opt->name;
+
+ check(ERR, (*p >= 'a' && *p <= 'z'),
+ "name for option [%d, %s] must start with in letter in [a..z]",
+ option_num, opt->name);
+
+ p++;
+
+ while(*p) {
+ check(ERR, ((*p >= 'a' && *p <= 'z') ||
+ (*p == '-') ||
+ (*p >= '0' && *p <= '9')),
+ "name for option [%d, %s] must only have the letters [-a..z0..9]",
+ option_num, opt->name);
+ p++;
+ }
+ }
+
+ optval = NULL;
+ optsize = 0;
+
+ switch(opt->type) {
+ case SANE_TYPE_BOOL:
+ check(WRN, (opt->size == sizeof(SANE_Word)),
+ "size of option %s is incorrect", opt->name);
+ optval = guards_malloc(opt->size);
+ optsize = opt->size;
+ check(WRN, (opt->constraint_type == SANE_CONSTRAINT_NONE),
+ "invalid constraint type for option [%d, %s] (%d)", option_num, opt->name, opt->constraint_type);
+ break;
+
+ case SANE_TYPE_INT:
+ case SANE_TYPE_FIXED:
+ check(WRN, (opt->size > 0 && (opt->size % sizeof(SANE_Word) == 0)),
+ "invalid size for option %s", opt->name);
+ optval = guards_malloc(opt->size);
+ optsize = opt->size;
+ check(WRN, (opt->constraint_type == SANE_CONSTRAINT_NONE ||
+ opt->constraint_type == SANE_CONSTRAINT_RANGE ||
+ opt->constraint_type == SANE_CONSTRAINT_WORD_LIST),
+ "invalid constraint type for option [%d, %s] (%d)", option_num, opt->name, opt->constraint_type);
+ break;
+
+ case SANE_TYPE_STRING:
+ check(WRN, (opt->size >= 1),
+ "size of option [%d, %s] must be at least 1 for the NUL terminator", option_num, opt->name);
+ check(INF, (opt->unit == SANE_UNIT_NONE),
+ "unit of option [%d, %s] is not SANE_UNIT_NONE", option_num, opt->name);
+ check(WRN, (opt->constraint_type == SANE_CONSTRAINT_STRING_LIST ||
+ opt->constraint_type == SANE_CONSTRAINT_NONE),
+ "invalid constraint type for option [%d, %s] (%d)", option_num, opt->name, opt->constraint_type);
+ optval = guards_malloc(opt->size);
+ optsize = opt->size;
+ break;
+
+ case SANE_TYPE_BUTTON:
+ case SANE_TYPE_GROUP:
+ check(INF, (opt->unit == SANE_UNIT_NONE),
+ "option [%d, %s], unit is not SANE_UNIT_NONE", option_num, opt->name);
+ check(INF, (opt->size == 0),
+ "option [%d, %s], size is not 0", option_num, opt->name);
+ check(WRN, (opt->constraint_type == SANE_CONSTRAINT_NONE),
+ "invalid constraint type for option [%d, %s] (%d)", option_num, opt->name, opt->constraint_type);
+ break;
+
+ default:
+ check(ERR, 0,
+ "invalid type %d for option %s",
+ opt->type, opt->name);
+ break;
+ }
+
+ if (optval) {
+ /* This is an option with a value */
+
+ /* get with NULL info.
+ *
+ * The SANE standard is not explicit on that subject. I
+ * consider that an inactive option shouldn't be read by a
+ * frontend because its value is meaningless. I think
+ * that, in that case, SANE_STATUS_INVAL is an appropriate
+ * return.
+ */
+ guards_set(optval, optsize);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_GET_VALUE, optval, NULL);
+ guards_check(optval, optsize);
+
+ if (SANE_OPTION_IS_GETTABLE (opt->cap)) {
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot get option [%d, %s] value, although it is active (%s)", option_num, opt->name, sane_strstatus(status));
+ } else {
+ check(ERR, (status == SANE_STATUS_INVAL),
+ "was able to get option [%d, %s] value, although it is not active", option_num, opt->name);
+ }
+
+ /* set with NULL info */
+ guards_set(optval, optsize);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, optval, NULL);
+ guards_check(optval, optsize);
+ if (SANE_OPTION_IS_SETTABLE (opt->cap) && SANE_OPTION_IS_ACTIVE (opt->cap)) {
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option [%d, %s] value, although it is active and settable (%s)", option_num, opt->name, sane_strstatus(status));
+ } else {
+ check(ERR, (status == SANE_STATUS_INVAL),
+ "was able to set option [%d, %s] value, although it is not active or settable", option_num, opt->name);
+ }
+
+ /* Get with invalid info. Since if is a get, info should be either
+ * ignored or set to 0. */
+ info = 0xdeadbeef;
+ guards_set(optval, optsize);
+ status = sane_control_option (device, option_num, SANE_ACTION_GET_VALUE,
+ optval, &info);
+ guards_check(optval, optsize);
+ if (SANE_OPTION_IS_GETTABLE (opt->cap)) {
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot get option [%d, %s] value, although it is active (%s)", option_num, opt->name, sane_strstatus(status));
+ } else {
+ check(ERR, (status == SANE_STATUS_INVAL),
+ "was able to get option [%d, %s] value, although it is not active", option_num, opt->name);
+ }
+ check(ERR, ((info == (SANE_Int)0xdeadbeef) || (info == 0)),
+ "when getting option [%d, %s], info was set to %x", option_num, opt->name, info);
+
+ /* Set with invalid info. Info should be reset by the backend. */
+ info = 0x10000;
+ guards_set(optval, optsize);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, optval, &info);
+ guards_check(optval, optsize);
+ if (SANE_OPTION_IS_SETTABLE (opt->cap) && SANE_OPTION_IS_ACTIVE (opt->cap)) {
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option [%d, %s] value, although it is active and settable (%s)", option_num, opt->name, sane_strstatus(status));
+
+ check(ERR, ((info & ~(SANE_INFO_INEXACT |
+ SANE_INFO_RELOAD_OPTIONS |
+ SANE_INFO_RELOAD_PARAMS)) == 0),
+ "sane_control_option set some wrong bit in info (%d)", info);
+
+ if (info & SANE_INFO_RELOAD_PARAMS) {
+ test_parameters(device, NULL);
+ }
+ } else {
+ check(ERR, (status == SANE_STATUS_INVAL),
+ "was able to set option [%d, %s] value, although it is not active or settable", option_num, opt->name);
+ }
+
+ /* Ask the backend to set the option automatically. */
+ guards_set(optval, optsize);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_AUTO, optval, &info);
+ guards_check(optval, optsize);
+ if (SANE_OPTION_IS_SETTABLE (opt->cap) &&
+ SANE_OPTION_IS_ACTIVE (opt->cap) &&
+ (opt->cap & SANE_CAP_AUTOMATIC)) {
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set the option [%d, %s] automatically.", option_num, opt->name);
+ } else {
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "was able to automatically set option [%d, %s], although it is not active or settable or automatically settable", option_num, opt->name);
+ }
+ if (info & SANE_INFO_RELOAD_PARAMS) {
+ test_parameters(device, NULL);
+ }
+ }
+
+ if (optval) {
+ guards_free(optval);
+ optval = NULL;
+ }
+
+ /* Some capabilities checks. */
+ check(ERR, ((opt->cap & (SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_SELECT)) !=
+ (SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_SELECT)),
+ "option [%d, %s], SANE_CAP_HARD_SELECT and SANE_CAP_SOFT_SELECT are mutually exclusive", option_num, opt->name);
+ if (opt->cap & SANE_CAP_SOFT_SELECT) {
+ check(ERR, ((opt->cap & SANE_CAP_SOFT_DETECT) != 0),
+ "option [%d, %s], SANE_CAP_SOFT_DETECT must be set if SANE_CAP_SOFT_SELECT is set", option_num, opt->name);
+ }
+ if ((opt->cap & (SANE_CAP_SOFT_SELECT |
+ SANE_CAP_HARD_SELECT |
+ SANE_CAP_SOFT_DETECT)) == SANE_CAP_SOFT_DETECT) {
+ check(ERR, (!SANE_OPTION_IS_SETTABLE (opt->cap)),
+ "option [%d, %s], must not be settable", option_num, opt->name);
+ }
+
+ if (!SANE_OPTION_IS_SETTABLE (opt->cap)) {
+ /* Unsettable option. Ignore the rest of the test. */
+ continue;
+ }
+
+ /* Check that will sane_control_option copy the string
+ * parameter and not just store a pointer to it. */
+ if (opt->type == SANE_TYPE_STRING) {
+ SANE_String val_string2;
+ char *optstr;
+
+ optstr = guards_malloc(opt->size);
+ val_string2 = guards_malloc(opt->size);
+
+ /* Poison the current value. */
+ strncpy(optstr, "-pOiSoN-", opt->size-1);
+ optstr[opt->size-1] = 0;
+
+ /* Get the value */
+ guards_set(optstr, opt->size);
+ status = sane_control_option (device, option_num, SANE_ACTION_GET_VALUE,
+ optstr, NULL);
+ guards_check(optstr, opt->size);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot get option [%d, %s] value", option_num, opt->name);
+ check(FATAL, (strcmp(optstr, "-pOiSoN-") != 0),
+ "sane_control_option did not set a value");
+
+ /* Set the value */
+ guards_set(optstr, opt->size);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, optstr, NULL);
+ guards_check(optstr, opt->size);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option [%d, %s] value", option_num, opt->name);
+
+ /* Poison the returned value. */
+ strncpy(optstr, "-pOiSoN-", opt->size-1);
+ optstr[opt->size-1] = 0;
+
+ /* Read again the value and compare. */
+ guards_set(val_string2, opt->size);
+ status = sane_control_option (device, option_num, SANE_ACTION_GET_VALUE,
+ val_string2, NULL);
+ guards_check(val_string2, opt->size);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot get option [%d, %s] value", option_num, opt->name);
+
+ check(FATAL, (strcmp(optstr, val_string2) != 0),
+ "sane_control_option did not copy the string parameter for option [%d, %s]", option_num, opt->name);
+
+ guards_free(optstr);
+ guards_free(val_string2);
+ }
+
+ /* Try both boolean options. */
+ if (opt->type == SANE_TYPE_BOOL) {
+ SANE_Bool org_v;
+ SANE_Bool v;
+
+ status = sane_control_option (device, option_num, SANE_ACTION_GET_VALUE,
+ &org_v, &info);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot get boolean option [%d, %s] value (%s)", option_num, opt->name, sane_strstatus(status));
+ /* Invert the condition. */
+ switch(org_v) {
+ case SANE_FALSE:
+ v = SANE_TRUE;
+ break;
+ case SANE_TRUE:
+ v = SANE_FALSE;
+ break;
+ default:
+ check(ERR, 0,
+ "invalid boolean value %d for option [%d, %s]",
+ org_v, option_num, opt->name);
+ }
+
+ /* Set the opposite of the current value. */
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &v, &info);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set boolean option [%d, %s] value (%s)", option_num, opt->name, sane_strstatus(status));
+ check(ERR, (v != org_v),
+ "boolean values should be different");
+
+ if (info & SANE_INFO_RELOAD_PARAMS) {
+ test_parameters(device, NULL);
+ }
+
+ /* Set the initial value. */
+ v = org_v;
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &v, &info);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set boolean option [%d, %s] value (%s)", option_num, opt->name, sane_strstatus(status));
+ check(ERR, (v == org_v),
+ "boolean values should be the same");
+
+ if (info & SANE_INFO_RELOAD_PARAMS) {
+ test_parameters(device, NULL);
+ }
+ }
+
+ /* Try to set an invalid option. */
+ switch(opt->type) {
+ case SANE_TYPE_BOOL: {
+ SANE_Word v; /* should be SANE_Bool instead */
+
+ v = -1; /* invalid value. must be SANE_FALSE or SANE_TRUE */
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &v, NULL);
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "was able to set an invalid value for boolean option [%d, %s]", option_num, opt->name);
+
+ v = 2; /* invalid value. must be SANE_FALSE or SANE_TRUE */
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &v, NULL);
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "was able to set an invalid value for boolean option [%d, %s]", option_num, opt->name);
+ }
+ break;
+
+ case SANE_TYPE_FIXED:
+ case SANE_TYPE_INT: {
+ SANE_Int *v;
+ unsigned int i;
+
+ v = guards_malloc(opt->size);
+
+ /* I can only think of a test for
+ * SANE_CONSTRAINT_RANGE. This tests the behaviour of
+ * sanei_constrain_value(). */
+ if (opt->constraint_type == SANE_CONSTRAINT_RANGE) {
+ for(i=0; i<opt->size / sizeof(SANE_Int); i++)
+ v[i] = opt->constraint.range->min - 1; /* invalid range */
+
+ guards_set(v, opt->size);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, v, &info);
+ guards_check(v, opt->size);
+ check(ERR, (status == SANE_STATUS_GOOD && (info & SANE_INFO_INEXACT) ),
+ "incorrect return when setting an invalid range value for option [%d, %s] (status %s, info %x)", option_num, opt->name, sane_strstatus(status), info);
+
+ /* Set the corrected value. */
+ guards_set(v, opt->size);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, v, &info);
+ guards_check(v, opt->size);
+ check(ERR, (status == SANE_STATUS_GOOD && !(info & SANE_INFO_INEXACT) ),
+ "incorrect return when setting an invalid range value for option [%d, %s] (status %s, info %x)", option_num, opt->name, sane_strstatus(status), info);
+
+
+ for(i=0; i<opt->size / sizeof(SANE_Int); i++)
+ v[i] = opt->constraint.range->max + 1; /* invalid range */
+
+ guards_set(v, opt->size);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, v, &info);
+ guards_check(v, opt->size);
+ check(ERR, (status == SANE_STATUS_GOOD && (info & SANE_INFO_INEXACT) ),
+ "incorrect return when setting an invalid range value for option [%d, %s] (status %s, info %x)", option_num, opt->name, sane_strstatus(status), info);
+
+ /* Set the corrected value. */
+ guards_set(v, opt->size);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, v, &info);
+ guards_check(v, opt->size);
+ check(ERR, (status == SANE_STATUS_GOOD && !(info & SANE_INFO_INEXACT) ),
+ "incorrect return when setting a valid range value for option [%d, %s] (status %s, info %x)", option_num, opt->name, sane_strstatus(status), info);
+ }
+
+ guards_free(v);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* TODO: button */
+
+ /*
+ * Here starts all the recursive stuff. After the test, it is
+ * possible that the value is not settable nor active
+ * anymore.
+ */
+
+ /* Try to set every option in a list */
+ switch(opt->constraint_type) {
+ case SANE_CONSTRAINT_WORD_LIST:
+ check(FATAL, (opt->constraint.word_list != NULL),
+ "no constraint list for option [%d, %s]", option_num, opt->name);
+ test_options_word_list (device, option_num, opt, can_do_recursive);
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ check(FATAL, (opt->constraint.string_list != NULL),
+ "no constraint list for option [%d, %s]", option_num, opt->name);
+ test_options_string_list (device, option_num, opt, can_do_recursive);
+ break;
+
+ case SANE_CONSTRAINT_RANGE:
+ check(FATAL, (opt->constraint.range != NULL),
+ "no constraint range for option [%d, %s]", option_num, opt->name);
+ check(FATAL, (opt->constraint.range->max >= opt->constraint.range->min),
+ "incorrect range for option [%d, %s] (min=%d > max=%d)",
+ option_num, opt->name, opt->constraint.range->min, opt->constraint.range->max);
+ /* Recurse. */
+ if (can_do_recursive) {
+ test_options(device, can_do_recursive-1);
+ }
+ break;
+
+ case SANE_CONSTRAINT_NONE:
+ check(INF, (opt->constraint.range == NULL),
+ "option [%d, %s] has some constraint value set", option_num, opt->name);
+
+ /* Recurse. */
+ if (can_do_recursive) {
+ test_options(device, can_do_recursive-1);
+ }
+ break;
+ }
+
+ /* End of the test for that option. */
+ }
+
+ /* test random non-existing options. */
+ opt = sane_get_option_descriptor (device, -1);
+ check(ERR, (opt == NULL),
+ "was able to get option descriptor for option -1");
+
+ opt = sane_get_option_descriptor (device, num_dev_options+1);
+ check(ERR, (opt == NULL),
+ "was able to get option descriptor for option %d", num_dev_options+1);
+
+ opt = sane_get_option_descriptor (device, num_dev_options+2);
+ check(ERR, (opt == NULL),
+ "was able to get option descriptor for option %d", num_dev_options+2);
+
+ opt = sane_get_option_descriptor (device, num_dev_options+50);
+ check(ERR, (opt == NULL),
+ "was able to get option descriptor for option %d", num_dev_options+50);
+}
+
+/* Get an option descriptor by the name of the option. */
+static const SANE_Option_Descriptor *get_optdesc_by_name(SANE_Handle device, const char *name, int *option_num)
+{
+ const SANE_Option_Descriptor *opt;
+ SANE_Int num_dev_options;
+ SANE_Status status;
+
+ /* Get the number of options. */
+ status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE, &num_dev_options, 0);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot get option 0 value (%s)", sane_strstatus(status));
+
+ for (*option_num = 0; *option_num < num_dev_options; (*option_num)++) {
+
+ /* Get the option descriptor */
+ opt = sane_get_option_descriptor (device, *option_num);
+ check(FATAL, (opt != NULL),
+ "cannot get option descriptor for option %d", *option_num);
+
+ if (opt->name && strcmp(opt->name, name) == 0) {
+ return(opt);
+ }
+ }
+ return(NULL);
+}
+
+/* Set the first value for an option. That equates to the minimum for a
+ * range or the first element in a list. */
+static void set_min_value(SANE_Handle device, int option_num,
+ const SANE_Option_Descriptor *opt)
+{
+ SANE_Status status;
+ SANE_String val_string;
+ SANE_Int val_int;
+ int rc;
+
+ check(BUG, (SANE_OPTION_IS_SETTABLE(opt->cap)),
+ "option is not settable");
+
+ switch(opt->constraint_type) {
+ case SANE_CONSTRAINT_WORD_LIST:
+ rc = check(ERR, (opt->constraint.word_list[0] > 0),
+ "no value in the list for option %s", opt->name);
+ if (!rc) return;
+ val_int = opt->constraint.word_list[1];
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to %d (%s)", opt->name, val_int, sane_strstatus(status));
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ rc = check(ERR, (opt->constraint.string_list[0] != NULL),
+ "no value in the list for option %s", opt->name);
+ if (!rc) return;
+ val_string = strdup(opt->constraint.string_list[0]);
+ assert(val_string);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, val_string, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to [%s] (%s)", opt->name, val_string, sane_strstatus(status));
+ free(val_string);
+ break;
+
+ case SANE_CONSTRAINT_RANGE:
+ val_int = opt->constraint.range->min;
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to %d (%s)", opt->name, val_int, sane_strstatus(status));
+ break;
+
+ default:
+ abort();
+ }
+}
+
+/* Set the last value for an option. That equates to the maximum for a
+ * range or the last element in a list. */
+static void set_max_value(SANE_Handle device, int option_num,
+ const SANE_Option_Descriptor *opt)
+{
+ SANE_Status status;
+ SANE_String val_string;
+ SANE_Int val_int;
+ int i;
+ int rc;
+
+ check(BUG, (SANE_OPTION_IS_SETTABLE(opt->cap)),
+ "option is not settable");
+
+ switch(opt->constraint_type) {
+ case SANE_CONSTRAINT_WORD_LIST:
+ rc = check(ERR, (opt->constraint.word_list[0] > 0),
+ "no value in the list for option %s", opt->name);
+ if (!rc) return;
+ val_int = opt->constraint.word_list[opt->constraint.word_list[0]];
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to %d (%s)", opt->name, val_int, sane_strstatus(status));
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ rc = check(ERR, (opt->constraint.string_list[0] != NULL),
+ "no value in the list for option %s", opt->name);
+ if (!rc) return;
+ for (i=1; opt->constraint.string_list[i] != NULL; i++);
+ val_string = strdup(opt->constraint.string_list[i-1]);
+ assert(val_string);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, val_string, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to [%s] (%s)", opt->name, val_string, sane_strstatus(status));
+ free(val_string);
+ break;
+
+ case SANE_CONSTRAINT_RANGE:
+ val_int = opt->constraint.range->max;
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to %d (%s)", opt->name, val_int, sane_strstatus(status));
+ break;
+
+ default:
+ abort();
+ }
+}
+
+/* Set a random value for an option amongst the possible values. */
+static void set_random_value(SANE_Handle device, int option_num,
+ const SANE_Option_Descriptor *opt)
+{
+ SANE_Status status;
+ SANE_String val_string;
+ SANE_Int val_int;
+ int i;
+ int rc;
+
+ check(BUG, (SANE_OPTION_IS_SETTABLE(opt->cap)),
+ "option is not settable");
+
+ switch(opt->constraint_type) {
+ case SANE_CONSTRAINT_WORD_LIST:
+ rc = check(ERR, (opt->constraint.word_list[0] > 0),
+ "no value in the list for option %s", opt->name);
+ if (!rc) return;
+ i=1+(rand() % opt->constraint.word_list[0]);
+ val_int = opt->constraint.word_list[i];
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to %d (%s)", opt->name, val_int, sane_strstatus(status));
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ rc = check(ERR, (opt->constraint.string_list[0] != NULL),
+ "no value in the list for option %s", opt->name);
+ if (!rc) return;
+ for (i=0; opt->constraint.string_list[i] != NULL; i++);
+ i = rand() % i;
+ val_string = strdup(opt->constraint.string_list[0]);
+ assert(val_string);
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, val_string, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to [%s] (%s)", opt->name, val_string, sane_strstatus(status));
+ free(val_string);
+ break;
+
+ case SANE_CONSTRAINT_RANGE:
+ i = opt->constraint.range->max - opt->constraint.range->min;
+ i = rand() % i;
+ val_int = opt->constraint.range->min + i;
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE, &val_int, NULL);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot set option %s to %d (%s)", opt->name, val_int, sane_strstatus(status));
+ break;
+
+ default:
+ abort();
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+/* Returns a string with the value of an option. */
+static char *get_option_value(SANE_Handle device, const char *option_name)
+{
+ const SANE_Option_Descriptor *opt;
+ void *optval; /* value for the option */
+ int optnum;
+ static char str[100];
+ SANE_Status status;
+
+ opt = get_optdesc_by_name(device, option_name, &optnum);
+ if (opt) {
+
+ optval = guards_malloc(opt->size);
+ status = sane_control_option (device, optnum,
+ SANE_ACTION_GET_VALUE, optval, NULL);
+
+ if (status == SANE_STATUS_GOOD) {
+ switch(opt->type) {
+
+ case SANE_TYPE_BOOL:
+ if (*(SANE_Word*) optval == SANE_FALSE) {
+ strcpy(str, "FALSE");
+ } else {
+ strcpy(str, "TRUE");
+ }
+ break;
+
+ case SANE_TYPE_INT:
+ sprintf(str, "%d", *(SANE_Word*) optval);
+ break;
+
+ case SANE_TYPE_FIXED: {
+ int i;
+ i = SANE_UNFIX(*(SANE_Word*) optval);
+ sprintf(str, "%d", i);
+ }
+ break;
+
+ case SANE_TYPE_STRING:
+ strcpy(str, optval);
+ break;
+
+ default:
+ str[0] = 0;
+ }
+ } else {
+ /* Shouldn't happen. */
+ strcpy(str, "backend default");
+ }
+
+ guards_free(optval);
+
+ } else {
+ /* The option does not exists. */
+ strcpy(str, "backend default");
+ }
+
+ return(str);
+}
+
+/* Display the parameters that used for a scan. */
+static char *display_scan_parameters(SANE_Handle device)
+{
+ static char str[150];
+ char *p = str;
+
+ *p = 0;
+
+ p += sprintf(p, "scan mode=[%s] ", get_option_value(device, SANE_NAME_SCAN_MODE));
+ p += sprintf(p, "resolution=[%s] ", get_option_value(device, SANE_NAME_SCAN_RESOLUTION));
+
+ p += sprintf(p, "tl_x=[%s] ", get_option_value(device, SANE_NAME_SCAN_TL_X));
+ p += sprintf(p, "tl_y=[%s] ", get_option_value(device, SANE_NAME_SCAN_TL_Y));
+ p += sprintf(p, "br_x=[%s] ", get_option_value(device, SANE_NAME_SCAN_BR_X));
+ p += sprintf(p, "br_y=[%s] ", get_option_value(device, SANE_NAME_SCAN_BR_Y));
+
+ return(str);
+}
+
+/* Do a scan to test the correctness of the backend. */
+static void test_scan(SANE_Handle device)
+{
+ const SANE_Option_Descriptor *opt;
+ SANE_Status status;
+ int option_num;
+ SANE_Int val_int;
+ unsigned char *image = NULL;
+ SANE_Parameters params;
+ size_t to_read;
+ SANE_Int len;
+ int ask_len;
+ int rc;
+ int fd;
+
+ /* Set the largest scan possible.
+ *
+ * For that test, the corner
+ * position must exists and be SANE_CONSTRAINT_RANGE (this is not
+ * a SANE requirement though).
+ */
+ opt = get_optdesc_by_name(device, SANE_NAME_SCAN_TL_X, &option_num);
+ if (opt) set_min_value(device, option_num, opt);
+
+ opt = get_optdesc_by_name(device, SANE_NAME_SCAN_TL_Y, &option_num);
+ if (opt) set_min_value(device, option_num, opt);
+
+ opt = get_optdesc_by_name(device, SANE_NAME_SCAN_BR_X, &option_num);
+ if (opt) set_max_value(device, option_num, opt);
+
+ opt = get_optdesc_by_name(device, SANE_NAME_SCAN_BR_Y, &option_num);
+ if (opt) set_max_value(device, option_num, opt);
+
+#define IMAGE_SIZE (512 * 1024)
+ image = guards_malloc(IMAGE_SIZE);
+
+ /* Try a read outside of a scan. */
+ status = sane_read (device, image, len, &len);
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "it is possible to sane_read outside of a scan");
+
+ /* Try to set the I/O mode outside of a scan. */
+ status = sane_set_io_mode (device, SANE_FALSE);
+ check(ERR, (status == SANE_STATUS_INVAL),
+ "it is possible to sane_set_io_mode outside of a scan");
+ status = sane_set_io_mode (device, SANE_TRUE);
+ check(ERR, (status == SANE_STATUS_INVAL ||
+ status == SANE_STATUS_UNSUPPORTED),
+ "it is possible to sane_set_io_mode outside of a scan");
+
+ /* Test sane_get_select_fd outside of a scan. */
+ status = sane_get_select_fd(device, &fd);
+ check(ERR, (status == SANE_STATUS_INVAL ||
+ status == SANE_STATUS_UNSUPPORTED),
+ "sane_get_select_fd outside of a scan returned an invalid status (%s)",
+ sane_strstatus (status));
+
+ if (test_level > 2) {
+ /* Do a scan, reading byte per byte */
+ check(MSG, 0, "TEST: scan byte per byte - %s", display_scan_parameters(device));
+
+ test_parameters(device, &params);
+ status = sane_start (device);
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot start the scan (%s)", sane_strstatus (status));
+ if (!rc) goto the_end;
+
+ /* sane_set_io_mode with SANE_FALSE is always supported. */
+ status = sane_set_io_mode (device, SANE_FALSE);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "sane_set_io_mode with SANE_FALSE must return SANE_STATUS_GOOD");
+
+ /* test sane_set_io_mode with SANE_TRUE. */
+ status = sane_set_io_mode (device, SANE_TRUE);
+ check(ERR, (status == SANE_STATUS_GOOD ||
+ status == SANE_STATUS_UNSUPPORTED),
+ "sane_set_io_mode with SANE_TRUE returned an invalid status (%s)",
+ sane_strstatus (status));
+
+ /* Put the backend back into blocking mode. */
+ status = sane_set_io_mode (device, SANE_FALSE);
+ check(ERR, (status == SANE_STATUS_GOOD),
+ "sane_set_io_mode with SANE_FALSE must return SANE_STATUS_GOOD");
+
+ /* Test sane_get_select_fd */
+ fd = 0x76575; /* won't exists */
+ status = sane_get_select_fd(device, &fd);
+ check(ERR, (status == SANE_STATUS_GOOD ||
+ status == SANE_STATUS_UNSUPPORTED),
+ "sane_get_select_fd returned an invalid status (%s)",
+ sane_strstatus (status));
+ if (status == SANE_STATUS_GOOD) {
+ check(ERR, (fd != 0x76575),
+ "sane_get_select_fd didn't set the fd although it should have");
+ check(ERR, (fd >= 0),
+ "sane_get_select_fd returned an invalid fd");
+ }
+
+ /* Check that it is not possible to set an option. It is probably
+ * a requirement stated indirectly in the section 4.4 on code
+ * flow.
+ */
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE,
+ &val_int , NULL);
+ check(WRN, (status != SANE_STATUS_GOOD),
+ "it is possible to set a value during a scan");
+
+ test_parameters(device, &params);
+
+ if (params.bytes_per_line != 0 && params.lines != 0) {
+
+ to_read = params.bytes_per_line * params.lines;
+ while(SANE_TRUE) {
+ len = 76457645; /* garbage */
+ guards_set(image, 1);
+ status = sane_read (device, image, 1, &len);
+ guards_check(image, 1);
+
+ if (status == SANE_STATUS_EOF) {
+ /* End of scan */
+ check(ERR, (len == 0),
+ "the length returned is not 0");
+ break;
+ }
+
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "scan stopped - status is %s", sane_strstatus (status));
+ if (!rc) {
+ check(ERR, (len == 0),
+ "the length returned is not 0");
+ break;
+ }
+
+ /* The scanner can only return 1. If it returns 0, we may
+ * loop forever. */
+ rc = check(ERR, (len == 1),
+ "backend returned 0 bytes - skipping test");
+ if (!rc) {
+ break;
+ }
+
+ to_read -= len;
+ }
+
+ if (params.lines != -1) {
+ check(ERR, (to_read == 0),
+ "scan ended, but data was truncated");
+ }
+ }
+
+ sane_cancel(device);
+ }
+
+ /* Try a read outside a scan. */
+ ask_len = 1;
+ guards_set(image, ask_len);
+ status = sane_read (device, image, ask_len, &len);
+ guards_check(image, ask_len);
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "it is possible to sane_read outside a scan");
+
+
+ /*
+ * Do a partial scan
+ */
+ check(MSG, 0, "TEST: partial scan - %s", display_scan_parameters(device));
+
+ status = sane_start (device);
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot start the scan (%s)", sane_strstatus (status));
+ if (!rc) goto the_end;
+
+ test_parameters(device, &params);
+
+ if (params.bytes_per_line != 0 && params.lines != 0) {
+
+ len = 10;
+
+ guards_set(image, 1);
+ status = sane_read (device, image, 1, &len);
+ guards_check(image, 1);
+
+ check(ERR, (len == 1),
+ "sane_read() didn't return 1 byte as requested");
+ }
+
+ sane_cancel(device);
+
+
+ /*
+ * Do a scan, reading random length.
+ */
+ check(MSG, 0, "TEST: scan random length - %s", display_scan_parameters(device));
+
+ test_parameters(device, &params);
+
+ /* Try a read outside a scan. */
+ ask_len = 20;
+ guards_set(image, ask_len);
+ status = sane_read (device, image, ask_len, &len);
+ guards_check(image, ask_len);
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "it is possible to sane_read outside a scan");
+
+ status = sane_start (device);
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot start the scan (%s)", sane_strstatus (status));
+ if (!rc) goto the_end;
+
+ /* Check that it is not possible to set an option. */
+ status = sane_control_option (device, option_num,
+ SANE_ACTION_SET_VALUE,
+ &val_int , NULL);
+ check(WRN, (status != SANE_STATUS_GOOD),
+ "it is possible to set a value during a scan");
+
+ test_parameters(device, &params);
+
+ if (params.bytes_per_line != 0 && params.lines != 0) {
+
+ to_read = params.bytes_per_line * params.lines;
+ srandom(time(NULL));
+
+ while (SANE_TRUE) {
+
+ ask_len = rand() & 0x7ffff; /* 0 to 512K-1 */
+ if (ask_len == 0) len = 1;
+ len = ask_len + 4978; /* garbage */
+
+ guards_set(image, ask_len);
+ status = sane_read (device, image, ask_len, &len);
+ guards_check(image, ask_len);
+
+ if (status == SANE_STATUS_EOF) {
+ /* End of scan */
+ check(ERR, (len == 0),
+ "the length returned is not 0");
+ break;
+ }
+
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "scan stopped - status is %s", sane_strstatus (status));
+ if (!rc) {
+ check(ERR, (len == 0),
+ "the length returned is not 0");
+ break;
+ }
+
+ /* The scanner cannot return 0. If it returns 0, we may
+ * loop forever. */
+ rc = check(ERR, (len > 0),
+ "backend didn't return any data - skipping test");
+ if (!rc) {
+ break;
+ }
+ rc = check(ERR, (len <= ask_len),
+ "backend returned too much data (%d / %d) - skipping test",
+ len, ask_len);
+ if (!rc) {
+ break;
+ }
+
+ to_read -= len;
+ }
+
+ if (params.lines != -1) {
+ check(ERR, (to_read == 0),
+ "scan ended, but data was truncated");
+ }
+ }
+
+ sane_cancel(device);
+
+ /* Try a read outside a scan. */
+ ask_len = 30;
+ guards_set(image, ask_len);
+ status = sane_read (device, image, ask_len, &len);
+ guards_check(image, ask_len);
+ check(ERR, (status != SANE_STATUS_GOOD),
+ "it is possible to sane_read outside a scan");
+
+ /*
+ * Do a scan with a fixed size and a big buffer
+ */
+ check(MSG, 0, "TEST: scan with a big max_len - %s", display_scan_parameters(device));
+
+ test_parameters(device, &params);
+
+ status = sane_start (device);
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "cannot start the scan (%s)", sane_strstatus (status));
+ if (!rc) goto the_end;
+
+ test_parameters(device, &params);
+
+ if (params.bytes_per_line != 0 && params.lines != 0) {
+
+ to_read = params.bytes_per_line * params.lines;
+ while(SANE_TRUE) {
+ ask_len = IMAGE_SIZE;
+ len = rand(); /* garbage */
+
+ guards_set(image, ask_len);
+ status = sane_read (device, image, ask_len, &len);
+ guards_check(image, ask_len);
+
+ if (status == SANE_STATUS_EOF) {
+ /* End of scan */
+ check(ERR, (len == 0),
+ "the length returned is not 0");
+ break;
+ }
+
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "scan stopped - status is %s", sane_strstatus (status));
+ if (!rc) {
+ check(ERR, (len == 0),
+ "the length returned is not 0");
+ break;
+ }
+
+ /* If the scanner return 0, we may loop forever. */
+ rc = check(ERR, (len > 0),
+ "backend didn't return any data - skipping test");
+ if (!rc) {
+ break;
+ }
+
+ rc = check(ERR, (len <= ask_len),
+ "backend returned too much data (%d / %d) - skipping test",
+ len, ask_len);
+ if (!rc) {
+ break;
+ }
+
+ to_read -= len;
+ }
+
+ if (params.lines != -1) {
+ check(ERR, (to_read == 0),
+ "scan ended, but data was truncated");
+ }
+ }
+
+ sane_cancel(device);
+
+ the_end:
+ if (image) guards_free(image);
+}
+
+/* Do several scans at different scan mode and resolution. */
+static void test_scans(SANE_Device * device)
+{
+ const SANE_Option_Descriptor *scan_mode_opt;
+ const SANE_Option_Descriptor *resolution_mode_opt;
+ SANE_Status status;
+ int scan_mode_optnum;
+ int resolution_mode_optnum;
+ SANE_String val_string;
+ int i;
+ int rc;
+
+ /* For that test, the requirements are:
+ * SANE_NAME_SCAN_MODE exists and is a SANE_CONSTRAINT_STRING_LIST
+ * SANE_NAME_SCAN_RESOLUTION exists and is either a SANE_CONSTRAINT_WORD_LIST or a SANE_CONSTRAINT_RANGE.
+ *
+ * These are not a SANE requirement, though.
+ */
+
+ scan_mode_opt = get_optdesc_by_name(device, SANE_NAME_SCAN_MODE, &scan_mode_optnum);
+ if (scan_mode_opt) {
+
+ rc = check(INF, (scan_mode_opt->type == SANE_TYPE_STRING),
+ "option [%s] is not a SANE_TYPE_STRING - skipping test", SANE_NAME_SCAN_MODE);
+ if (!rc) return;
+ rc = check(INF, (scan_mode_opt->constraint_type == SANE_CONSTRAINT_STRING_LIST),
+ "constraint for option [%s] is not SANE_CONSTRAINT_STRING_LIST - skipping test", SANE_NAME_SCAN_MODE);
+ if (!rc) return;
+ rc = check(INF, (SANE_OPTION_IS_SETTABLE(scan_mode_opt->cap)),
+ "option [%s] is not settable - skipping test", SANE_NAME_SCAN_MODE);
+ if (!rc) return;
+ }
+
+ resolution_mode_opt = get_optdesc_by_name(device, SANE_NAME_SCAN_RESOLUTION, &resolution_mode_optnum);
+ if (resolution_mode_opt) {
+ rc = check(INF, (SANE_OPTION_IS_SETTABLE(resolution_mode_opt->cap)),
+ "option [%s] is not settable - skipping test", SANE_NAME_SCAN_RESOLUTION);
+ if (!rc) return;
+ }
+
+ if (scan_mode_opt) {
+ /* Do several scans, with several resolution. */
+ for (i=0; scan_mode_opt->constraint.string_list[i] != NULL; i++) {
+
+ val_string = strdup(scan_mode_opt->constraint.string_list[i]);
+ assert(val_string);
+
+ status = sane_control_option (device, scan_mode_optnum,
+ SANE_ACTION_SET_VALUE, val_string, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "cannot set a settable option (status=%s)", sane_strstatus(status));
+
+ free(val_string);
+
+ if (resolution_mode_opt) {
+ set_min_value(device, resolution_mode_optnum,
+ resolution_mode_opt);
+ test_scan(device);
+
+ set_max_value(device, resolution_mode_optnum,
+ resolution_mode_opt);
+ test_scan(device);
+
+ set_random_value(device, resolution_mode_optnum,
+ resolution_mode_opt);
+ test_scan(device);
+ } else {
+ test_scan(device);
+ }
+ }
+ } else {
+ if (resolution_mode_opt) {
+ set_min_value(device, resolution_mode_optnum,
+ resolution_mode_opt);
+ test_scan(device);
+
+ set_max_value(device, resolution_mode_optnum,
+ resolution_mode_opt);
+ test_scan(device);
+
+ set_random_value(device, resolution_mode_optnum,
+ resolution_mode_opt);
+ test_scan(device);
+ } else {
+ test_scan(device);
+ }
+ }
+}
+
+/** test sane_get_devices
+ * test sane_get_device function, if time is greter than 0,
+ * loop to let tester plug/unplug device to check for correct
+ * hotplug detection
+ * @param device_list device list to fill
+ * @param time time to loop
+ * @return 0 on success
+ */
+static int test_get_devices(const SANE_Device ***device_list, int time)
+{
+int loop=0;
+int i;
+const SANE_Device *dev;
+SANE_Status status;
+
+ status = sane_get_devices (device_list, SANE_TRUE);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_get_devices() failed (%s)", sane_strstatus (status));
+
+ /* Verify that the SANE doc (or tstbackend) is up to date */
+ for (i=0; (*device_list)[i] != NULL; i++) {
+
+ dev = (*device_list)[i];
+
+ check(FATAL, (dev->name != NULL), "device name is NULL");
+ check(FATAL, (dev->vendor != NULL), "device vendor is NULL");
+ check(FATAL, (dev->type != NULL), "device type is NULL");
+ check(FATAL, (dev->model != NULL), "device model is NULL");
+
+ check(INF, ((strcmp(dev->type, "flatbed scanner") == 0) ||
+ (strcmp(dev->type, "frame grabber") == 0) ||
+ (strcmp(dev->type, "handheld scanner") == 0) ||
+ (strcmp(dev->type, "still camera") == 0) ||
+ (strcmp(dev->type, "video camera") == 0) ||
+ (strcmp(dev->type, "virtual device") == 0) ||
+ (strcmp(dev->type, "film scanner") == 0) ||
+ (strcmp(dev->type, "multi-function peripheral") == 0) ||
+ (strcmp(dev->type, "sheetfed scanner") == 0)),
+ "unknown device type [%s]. Update SANE doc section \"Type Strings\"", dev->type);
+
+ check(INF, (
+ (strcmp(dev->vendor, "AGFA") == 0) ||
+ (strcmp(dev->vendor, "Abaton") == 0) ||
+ (strcmp(dev->vendor, "Acer") == 0) ||
+ (strcmp(dev->vendor, "Apple") == 0) ||
+ (strcmp(dev->vendor, "Artec") == 0) ||
+ (strcmp(dev->vendor, "Avision") == 0) ||
+ (strcmp(dev->vendor, "CANON") == 0) ||
+ (strcmp(dev->vendor, "Connectix") == 0) ||
+ (strcmp(dev->vendor, "Epson") == 0) ||
+ (strcmp(dev->vendor, "Fujitsu") == 0) ||
+ (strcmp(dev->vendor, "Gphoto2") == 0) ||
+ (strcmp(dev->vendor, "Hewlett-Packard") == 0) ||
+ (strcmp(dev->vendor, "IBM") == 0) ||
+ (strcmp(dev->vendor, "Kodak") == 0) ||
+ (strcmp(dev->vendor, "Lexmark") == 0) ||
+ (strcmp(dev->vendor, "Logitech") == 0) ||
+ (strcmp(dev->vendor, "Microtek") == 0) ||
+ (strcmp(dev->vendor, "Minolta") == 0) ||
+ (strcmp(dev->vendor, "Mitsubishi") == 0) ||
+ (strcmp(dev->vendor, "Mustek") == 0) ||
+ (strcmp(dev->vendor, "NEC") == 0) ||
+ (strcmp(dev->vendor, "Nikon") == 0) ||
+ (strcmp(dev->vendor, "Noname") == 0) ||
+ (strcmp(dev->vendor, "Plustek") == 0) ||
+ (strcmp(dev->vendor, "Polaroid") == 0) ||
+ (strcmp(dev->vendor, "Relisys") == 0) ||
+ (strcmp(dev->vendor, "Ricoh") == 0) ||
+ (strcmp(dev->vendor, "Sharp") == 0) ||
+ (strcmp(dev->vendor, "Siemens") == 0) ||
+ (strcmp(dev->vendor, "Tamarack") == 0) ||
+ (strcmp(dev->vendor, "UMAX") == 0)),
+ "unknown device vendor [%s]. Update SANE doc section \"Vendor Strings\"", dev->vendor);
+ }
+
+ /* loop on detecting device to let time to plug/unplug scanners */
+ while(loop<time) {
+ /* print and free detected device list */
+ check(MSG, 0, "DETECTED DEVICES:");
+ for (i=0; (*device_list)[i] != NULL; i++) {
+ dev = (*device_list)[i];
+ check(MSG, 0, "\t%s:%s %s:%s", dev->vendor, dev->name, dev->type, dev->model);
+ }
+ if(i==0) {
+ check(MSG, 0, "\tnone...");
+ }
+ sleep(1);
+ (*device_list) = NULL;
+ status = sane_get_devices (device_list, SANE_TRUE);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_get_devices() failed (%s)", sane_strstatus (status));
+ loop++;
+ }
+ return 0;
+}
+
+static void usage(const char *execname)
+{
+ printf("Usage: %s [-d backend_name] [-l test_level] [-r recursion_level] [-g time (s)]\n", execname);
+ printf("\t-v\tverbose level\n");
+ printf("\t-d\tbackend name\n");
+ printf("\t-l\tlevel of testing (0=some, 1=0+options, 2=1+scans, 3=longest tests)\n");
+ printf("\t-r\trecursion level for option testing (the higher, the longer)\n");
+ printf("\t-g\ttime to loop on sane_get_devices function to test scannet hotplug detection (time is in seconds).\n");
+}
+
+int
+main (int argc, char **argv)
+{
+ char *devname = NULL;
+ SANE_Status status;
+ SANE_Int version_code;
+ SANE_Handle device;
+ int ch;
+ int index;
+ int i;
+ const SANE_Device **device_list;
+ int rc;
+ int recursion_level;
+ int time;
+
+ printf("tstbackend, Copyright (C) 2002 Frank Zago\n");
+ printf("tstbackend comes with ABSOLUTELY NO WARRANTY\n");
+ printf("This is free software, and you are welcome to redistribute it\n");
+ printf("under certain conditions. See COPYING file for details\n\n");
+ printf("This is tstbackend build %d\n\n", BUILD);
+
+ /* Read the command line options. */
+ opterr = 0;
+ recursion_level = 5; /* 5 levels or recursion should be enough */
+ test_level = 0; /* basic tests only */
+ time = 0; /* no get devices loop */
+
+ while ((ch = getopt_long (argc, argv, "-v:d:l:r:g:h", basic_options,
+ &index)) != EOF) {
+ switch(ch) {
+ case 'v':
+ verbose_level = atoi(optarg);
+ break;
+
+ case 'd':
+ devname = strdup(optarg);
+ break;
+
+ case 'l':
+ test_level = atoi(optarg);
+ if (test_level < 0 || test_level > 4) {
+ fprintf(stderr, "invalid test_level\n");
+ return(1);
+ }
+ break;
+
+ case 'r':
+ recursion_level = atoi(optarg);
+ break;
+
+ case 'g':
+ time = atoi(optarg);
+ break;
+
+ case 'h':
+ usage(argv[0]);
+ return(0);
+
+ case '?':
+ fprintf(stderr, "invalid option\n");
+ return(1);
+
+ default:
+ fprintf(stderr, "bug in tstbackend\n");
+ return(1);
+ }
+ }
+
+ /* First test */
+ check(MSG, 0, "TEST: init/exit");
+ for (i=0; i<10; i++) {
+ /* Test 1. init/exit with a version code */
+ status = sane_init(&version_code, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_init failed with %s", sane_strstatus (status));
+ check(FATAL, (SANE_VERSION_MAJOR(version_code) == 1),
+ "invalid SANE version linked");
+ sane_exit();
+
+ /* Test 2. init/exit without a version code */
+ status = sane_init(NULL, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_init failed with %s", sane_strstatus (status));
+ sane_exit();
+
+ /* Test 3. Init/get_devices/open invalid/exit */
+ status = sane_init(NULL, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_init failed with %s", sane_strstatus (status));
+
+ status = sane_get_devices (&device_list, SANE_TRUE);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_get_devices() failed (%s)", sane_strstatus (status));
+
+ status = sane_open ("opihndvses75bvt6fg", &device);
+ check(WRN, (status == SANE_STATUS_INVAL),
+ "sane_open() failed (%s)", sane_strstatus (status));
+
+ if (status == SANE_STATUS_GOOD)
+ sane_close(device);
+
+ sane_exit();
+
+ /* Test 4. Init/get_devices/open default/exit */
+ status = sane_init(NULL, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_init failed with %s", sane_strstatus (status));
+
+ status = sane_get_devices (&device_list, SANE_TRUE);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_get_devices() failed (%s)", sane_strstatus (status));
+
+ status = sane_open ("", &device);
+ if (status == SANE_STATUS_GOOD)
+ sane_close(device);
+
+ sane_exit();
+ }
+
+ status = sane_init (&version_code, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_init failed with %s", sane_strstatus (status));
+
+ /* Check the device list */
+ rc = test_get_devices(&device_list, time);
+ if (rc) goto the_exit;
+
+ if (!devname) {
+ /* If no device name was specified explicitly, we look at the
+ environment variable SANE_DEFAULT_DEVICE. If this variable
+ is not set, we open the first device we find (if any): */
+ devname = getenv ("SANE_DEFAULT_DEVICE");
+ if (devname) devname = strdup(devname);
+ }
+
+ if (!devname) {
+ if (device_list[0]) {
+ devname = strdup(device_list[0]->name);
+ }
+ }
+
+ rc = check(ERR, (devname != NULL),
+ "no SANE devices found");
+ if (!rc) goto the_exit;
+
+ check(MSG, 0, "using device %s", devname);
+
+ /* Test open close */
+ check(MSG, 0, "TEST: open/close");
+ for (i=0; i<10; i++) {
+ status = sane_open (devname, &device);
+ rc = check(ERR, (status == SANE_STATUS_GOOD),
+ "sane_open failed with %s for device %s", sane_strstatus (status), devname);
+ if (!rc) goto the_exit;
+ sane_close (device);
+ }
+
+ if (test_level < 1) {
+ sane_exit();
+ goto the_exit;
+ }
+
+
+ /* Test options */
+ check(MSG, 0, "TEST: options consistency");
+ status = sane_open (devname, &device);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_open failed with %s for device %s", sane_strstatus (status), devname);
+
+ test_parameters(device, NULL);
+ test_options(device, recursion_level);
+ sane_close (device);
+ sane_exit();
+
+ if (test_level < 2) {
+ goto the_exit;
+ }
+
+
+ /* Test scans */
+ check(MSG, 0, "TEST: scan test");
+ status = sane_init (&version_code, NULL);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_init failed with %s", sane_strstatus (status));
+ status = sane_open (devname, &device);
+ check(FATAL, (status == SANE_STATUS_GOOD),
+ "sane_open failed with %s for device %s", sane_strstatus (status), devname);
+ test_scans(device);
+ sane_close (device);
+ sane_exit();
+
+ the_exit:
+ if (devname) free(devname);
+ display_stats();
+ return(0);
+}
+
+
+