diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-10-06 14:00:40 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-10-06 14:00:40 +0200 |
commit | 6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch) | |
tree | 2e301d871bbeeb44aa57ff9cc070fcf3be484487 /frontend |
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/Makefile.am | 34 | ||||
-rw-r--r-- | frontend/Makefile.in | 772 | ||||
-rw-r--r-- | frontend/saned.c | 3355 | ||||
-rw-r--r-- | frontend/scanimage.c | 2365 | ||||
-rw-r--r-- | frontend/stiff.c | 610 | ||||
-rw-r--r-- | frontend/stiff.h | 20 | ||||
-rw-r--r-- | frontend/test.c | 182 | ||||
-rw-r--r-- | frontend/tstbackend.c | 1871 |
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, ¶ms); + 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, ¶ms); + + 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, ¶ms); + + 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, ¶ms); + + /* 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, ¶ms); + + 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, ¶ms); + + 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, ¶ms); + + 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); +} + + + |