diff options
Diffstat (limited to 'backend/cfg-obj.c')
-rw-r--r-- | backend/cfg-obj.c | 1619 |
1 files changed, 1619 insertions, 0 deletions
diff --git a/backend/cfg-obj.c b/backend/cfg-obj.c new file mode 100644 index 0000000..e26901c --- /dev/null +++ b/backend/cfg-obj.c @@ -0,0 +1,1619 @@ +/* cfg-obj.c -- configuration objects + * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +/*! \file + * \todo Split the probing responsibility off into the respective + * channel classes. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "cfg-obj.h" + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> + +#include "command.h" +#include "hw-data.h" +#include "net-obj.h" +#include "model-info.h" +#include "ipc.h" +#include "utils.h" + +/* Deprecated includes */ +#include "include/sane/sanei_usb.h" +#include "include/sane/sanei_scsi.h" + + +#ifndef SANE_CONFIG_DIR_NAME +#define SANE_CONFIG_DIR_NAME "/etc/sane.d" +#endif + +#define DEV_NAME_SEP_STR ":" +#define DEV_NAME_SEP DEV_NAME_SEP_STR[0] + + +const char *cfg_file_name = "epkowa.conf"; + + +typedef struct +{ + bool active[_CFG_KEY_ID_TERMINATOR_]; + list * seen[_CFG_KEY_ID_TERMINATOR_]; + +} cfg_type; + +static cfg_type *_cfg = NULL; + +static cfg_key_type _cfg_key[] = + { + "net", + "pio", + "scsi", + "usb", + + "interpreter", + "fs-blacklist", + + "option", + }; + +static char* _opt_vals[] = + { + "prefer-adf", + }; + + +static FILE *_cfg_fopen_conf (const char *filename); +static FILE *_cfg_fopen_data (const char *dirname, const char *key); + + +static cfg_key_id_type _cfg_getline (char **line, size_t *size, FILE *fp); + + +static bool _cfg_is_bare_key (const char *string); +static bool _cfg_is_valid_net_entry (const char *string); +static bool _cfg_is_valid_scsi_entry (const char *string); +static bool _cfg_is_valid_usb_entry (const char *string); +static bool _cfg_is_valid_interpreter_entry (const char *string); +static bool _cfg_is_valid_fs_blacklist_entry (const char *string); +static bool _cfg_is_valid_option_entry (const char *string); + +typedef bool (*validator) (const char *); + +static validator _cfg_validate[] = + { + _cfg_is_valid_net_entry, + _cfg_is_bare_key, + _cfg_is_valid_scsi_entry, + _cfg_is_valid_usb_entry, + + _cfg_is_valid_interpreter_entry, + _cfg_is_valid_fs_blacklist_entry, + + _cfg_is_valid_option_entry, + }; + + +static bool _cfg_register_no_op (const char *string); +static bool _cfg_register_net_entry (const char *string); +static bool _cfg_register_scsi_entry (const char *string); +static bool _cfg_register_usb_entry (const char *string); +static bool _cfg_register_interpreter_entry (const char *string); +static bool _cfg_register_fs_blacklist_entry (const char *string); +static bool _cfg_register_option_entry (const char *string); + +typedef bool (*registrar) (const char *); + +static registrar _cfg_register[] = + { + _cfg_register_net_entry, + _cfg_register_no_op, + _cfg_register_scsi_entry, + _cfg_register_usb_entry, + + _cfg_register_interpreter_entry, + _cfg_register_fs_blacklist_entry, + + _cfg_register_option_entry, + }; + + +static void _cfg_probe_no_op (list *dev_list); +static void _cfg_probe_net (list *dev_list); +static void _cfg_probe_scsi (list *dev_list); +static void _cfg_probe_usb (list *dev_list); +static void _cfg_probe_interpreter (list *dev_list); + +typedef void (*probe) (list *); + +static probe _cfg_probe[] = + { + _cfg_probe_net, + _cfg_probe_no_op, + _cfg_probe_scsi, + _cfg_probe_usb, + + _cfg_probe_interpreter, + _cfg_probe_no_op, + + _cfg_probe_no_op, + }; + + +static SANE_Status _cfg_attach (SANE_String_Const dev_name, list *dev_list); +static SANE_String_Const _cfg_get_vendor (SANE_String_Const dev_name); +static SANE_String_Const _cfg_get_model (SANE_String_Const dev_name); +static SANE_String_Const _cfg_get_type (SANE_String_Const dev_name); + +static bool _cfg_have_interpreter (const char *library, const char *firmware); + +/* Helper stuff to hack around sanei_usb/sanei_scsi API requirements + used in the various _cfg_probe_*() implementations. + */ +static list *_cfg_dev_list = NULL; +static const char *_cfg_dev_key = NULL; +static SANE_Status _cfg_scsi_attach (SANE_String_Const dev_name); +static SANE_Status _cfg_usb_attach (SANE_String_Const dev_name); + + +static void _cfg_net_dtor (void *); +static void _cfg_scsi_dtor (void *); +static void _cfg_interpreter_dtor (void *); + +typedef void (*destructor) (void *); + +static destructor _cfg_dtor[] = + { + _cfg_net_dtor, + free, + _cfg_scsi_dtor, + free, + + _cfg_interpreter_dtor, + free, + + free, + }; + + +/*! Creates and initialises a configuration object. + + The configuration object is initialised with per key data files in + the \a pkgdatadir and a configuration file, \c cfg_file_name. The + configuration file is searched for in list of directories with the + first file found being used. + + The list of directories can be customised via a \c SANE_CONFIG_DIR + environment variable. This variable uses the exact same syntax as + the \c PATH environment variable with this difference that a final + colon, \c :, appends the default locations. + + When no \c SANE_CONFIG_DIR variable is set, the default locations + will be used. The default locations are, in order: + - the process' current working directory + - the system's SANE configuration directory + + On most system's the above is equivalent to setting: + \code + SANE_CONFIG_DIR=.:/etc/sane.d + \endcode + */ +void * +cfg_init (const char *pkgdatadir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + char *lc_ctype = NULL; + FILE *fp = NULL; + cfg_key_id_type id; + + log_call ("(%s, %p)", pkgdatadir, status); + require (num_of (_cfg_key) == _CFG_KEY_ID_TERMINATOR_); + + if (_cfg) + { + if (pkgdatadir) err_minor ("been here, done that"); + if (status) *status = s; + return _cfg; + } + + _cfg = t_calloc (1, cfg_type); + if (!_cfg) + { + if (status) *status = SANE_STATUS_NO_MEM; + return _cfg; + } + + lc_ctype = setlocale (LC_CTYPE, "C"); + + if (pkgdatadir) + { + /* Look for data files containing information about the + * devices we "officially" support. Keywords without a + * corresponding data file are not an error. + */ + id = 0; + while (num_of (_cfg_key) > id) + { + fp = _cfg_fopen_data (pkgdatadir, _cfg_key[id]); + if (fp) + { + size_t size = 0; + char *line = NULL; + + while (_cfg_getline (&line, &size, fp) == id) + { + log_info ("line: '%s'", line); + + if (0 != strcmp_c (line, _cfg_key[id])) + { + _cfg_register[id] (line); + } + } + delete (line); + if (fclose (fp)) + { + err_minor ("%s%s%s: %s", + pkgdatadir, FILE_SEP_STR, _cfg_key[id], + strerror (errno)); + } + } + ++id; + } + + /* Allow for interpreter based packages to (un)register + * themselves upon (un)installation in a suitable place + * so we don't have to track them anymore. + */ + id = CFG_KEY_INTERPRETER; + fp = _cfg_fopen_data (PKGLOCALSTATEDIR, _cfg_key[id]); + if (fp) + { + size_t size = 0; + char *line = NULL; + + while (_cfg_getline (&line, &size, fp) == id) + { + log_info ("line: '%s'", line); + + if (0 != strcmp_c (line, _cfg_key[id])) + { + _cfg_register[id] (line); + } + } + delete (line); + if (fclose (fp)) + { + err_minor ("%s%s%s: %s", + PKGLOCALSTATEDIR, FILE_SEP_STR, _cfg_key[id], + strerror (errno)); + } + } + } + + fp = _cfg_fopen_conf (cfg_file_name); + if (fp) + { + size_t size = 0; + char *line = NULL; + + while (num_of (_cfg_key) != (id = _cfg_getline (&line, &size, fp))) + { + log_info ("line: '%s'", line); + + _cfg->active[id] = true; + + if (0 != strcmp_c (line, _cfg_key[id])) + { + _cfg_register[id] (line); + } + } + delete (line); + if (fclose (fp)) + { + err_minor ("%s: %s", cfg_file_name, strerror (errno)); + } + } + + setlocale (LC_CTYPE, lc_ctype); + + /* For backwards compatibity with older configuration files. + */ + if (cfg_has (_cfg, CFG_KEY_USB) + && 0 < list_size (_cfg->seen[CFG_KEY_INTERPRETER])) + { + log_info ("enabling interpreter support"); + cfg_set (_cfg, CFG_KEY_INTERPRETER, true); + } + + if (status) *status = s; + return _cfg; +} + +/*! Releases resources acquired by a configuration object. + + Always returns \c NULL. + + \sa cfg_init + */ +void * +cfg_exit (void *self) +{ + log_call ("(%p)", self); + require (_cfg == self); + + if (_cfg) + { + int id; + + for (id = 0; num_of (_cfg_key) > id; ++id) + { + if (_cfg->seen[id]) + { + list_destroy (_cfg->seen[id], _cfg_dtor[id]); + _cfg->seen[id] = NULL; + } + } + + delete (_cfg); + } + return _cfg; +} + + +/*! Searches for supported devices and adds them to a \a dev_list. + */ +void +cfg_find (const void *self, cfg_key_id_type id, list *dev_list) +{ + log_call ("(%p, %u, %p)", self, id, dev_list); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + if (!cfg_has (self, id)) return; + + _cfg_probe[id] (dev_list); +} + +/*! Provides read-only access to the supported devices found. + + \bug Access is not read-only. + */ +list * +cfg_seen (const void *self, cfg_key_id_type id) +{ + log_call ("(%p, %u)", self, id); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + return _cfg->seen[id]; +} + +/*! Tells whether configuration for a key \a id is in effect. + + \sa cfg_set + */ +bool +cfg_has (const void *self, cfg_key_id_type id) +{ + log_call ("(%p, %u)", self, id); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + return _cfg->active[id]; +} + +/*! Enables or disabled support for a configuration key. + + This can be used to disable configured functionality when the + required components are not available. Think network support + and interpreter based scanners. + */ +void +cfg_set (void *self, cfg_key_id_type id, bool value) +{ + log_call ("(%p, %u, %u)", self, id, value); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + _cfg->active[id] = value; +} + + +bool +cfg_has_value (const void *self, cfg_key_id_type id, const char* val) +{ + list *seen = cfg_seen (self, id); + const char *found = NULL; + + if (val && seen) + { + list_entry *cur = seen->cur; + list_reset (seen); + + while ((found = list_next (seen)) + && 0 != strcmp_c (val, found)) + { + /* condition does all the processing */ + } + seen->cur = cur; + } + log_info ("check for %s in %s: %s", val, _cfg_key[id], + (NULL != found) ? "found" : "not found"); + return (NULL != found); +} + + +/*! Returns the string that corresponds to the key \a id. + */ +cfg_key_type +cfg_key (const void *self, cfg_key_id_type id) +{ + log_call ("(%p, %u)", self, id); + require (_cfg && _cfg == self); + require (0 <= id && id < num_of (_cfg_key)); + + return _cfg_key[id]; +} + + +/*! Tries to find and open a configuration file. + + Returns a valid file pointer if successfull, \c NULL otherwise. + + \sa cfg_init + */ +static FILE * +_cfg_fopen_conf (const char *name) +{ + const char *default_path = "." PATH_SEP_STR SANE_CONFIG_DIR_NAME; + + char *path = getenv ("SANE_CONFIG_DIR"); + char *next; + char *dir; + FILE *fp; + + log_call ("(%s)", name); + require (name); + + if (path) + { + size_t len = strlen (path); + char *p; + + if (0 < len && PATH_SEP == path[len-1]) + { + len += strlen (default_path); + } + + p = t_malloc (len + 1, char); + if (p) + { + strcpy (p, path); + if (strlen (path) < len) + { + strcat (p, default_path); + } + path = strdup (p); + delete (p); + } + else + { + err_major ("SANE_CONFIG_DIR: %s", strerror (ENOMEM)); + } + } + else + { + path = strdup (default_path); + } + + if (!path) + { + errno = ENOMEM; + return NULL; + } + + fp = NULL; + next = path; + + while (!fp && (dir = strsep (&next, PATH_SEP_STR))) + { + fp = _cfg_fopen_data (dir, name); + } + delete (path); + + return fp; +} + +/*! Tries to open a configuration or data file. + + Returns a valid file pointer if successfull, \c NULL otherwise. + + \sa cfg_init + */ +static FILE * +_cfg_fopen_data (const char *dir, const char *name) +{ + FILE *fp = NULL; + char file[PATH_MAX]; + int n = snprintf (file, sizeof (file), "%s%c%s", dir, FILE_SEP, name); + + log_call ("(%s, %s)", dir, name); + require (dir && name); + + if (sizeof (file) > n) + { + fp = fopen (file, "rb"); + if (!fp) + { + log_info ("%s: %s", file, strerror (errno)); + } + } + else + { + err_minor ("%s%c%s: %s", + dir, FILE_SEP, name, strerror (ENAMETOOLONG)); + } + if (fp) log_info ("using '%s'", file); + + return fp; +} + +/*! Returns a validated line of a configuration or data file. + + Validated lines are guaranteed to start with one of the entries in + \c _cfg_key and adhere to the syntax for that particular key. + + Comments and empty lines will be silently ignored, invalid entries + will be logged and skipped and leading and/or trailing white space + as well as trailing comments will be stripped. + + Comments start with a sharp, \c #. An escape mechanism is \e not + supported. + + Returns \c _CFG_KEY_ID_TERMINATOR_ if no validated line can be + found. + */ +static cfg_key_id_type +_cfg_getline (char **line, size_t *size, FILE *fp) +{ + int id = num_of (_cfg_key); + bool valid = false; + ssize_t n; + + char *lc_ctype; + + require (line && size && fp); + + lc_ctype = setlocale (LC_CTYPE, "C"); + + while (!valid && -1 != (n = getline (line, size, fp))) + { + char *s; + + log_data ("looking at '%s'", *line); + + s = strchr (*line, '#'); + if (s) *s = '\0'; /* chomp trailing comments */ + + s = *line; + n = strlen (s); + + while (0 < n && (isspace (*s))) /* whitespace removal */ + --n, ++s; + while (0 < n && (isspace (s[n-1]))) + --n, s[n] = '\0'; + + log_data ("payload is '%s'", s); + + require (strlen (s) == n); + + if (0 < n) /* content validation */ + { + id = 0; + while (num_of (_cfg_key) > id + && 0 != strncmp_c (s, _cfg_key[id], strlen (_cfg_key[id]))) + { + log_data ("%s !~ %s", _cfg_key[id], s); + ++id; + } + + valid = (num_of (_cfg_key) > id) && _cfg_validate[id] (s); + if (valid && s != *line) + { + memmove (*line, s, strlen (s) + 1); + } + if (!valid) + { + err_major ("invalid: '%s'", s); + } + } + } + + setlocale (LC_CTYPE, lc_ctype); + + return (-1 == n ? num_of (_cfg_key) : id); +} + +/*! Tells whether a \a string consists of a single key. + */ +static bool +_cfg_is_bare_key (const char *string) +{ + int id = 0; + + require (string); + + while (num_of (_cfg_key) > id + && 0 != strcmp_c (string, _cfg_key[id])) + { + ++id; + } + return (num_of (_cfg_key) > id); +} + +/*! Says whether a \a string makes up a valid network device entry. + * + * Valid network device entries have one of the following formats: + * - \c net IPv4-address[( |:)port] + * - \c net hostname[( |:)port] + * + * \todo Tighten up validation! + * \todo Consider support for IPv4-address ranges by use of netmasks + * or CIDR notation. + */ +static bool +_cfg_is_valid_net_entry (const char *string) +{ + const char *p = string; + + int port = 0; + char c = '\0'; + + require (string); + + if (0 == strcmp_c (string, _cfg_key[CFG_KEY_NET])) + return false; + + if (0 != strncmp_c (string, _cfg_key[CFG_KEY_NET], + strlen (_cfg_key[CFG_KEY_NET])) + || !isspace (string[strlen(_cfg_key[CFG_KEY_NET])])) + return false; + + p += strlen (_cfg_key[CFG_KEY_NET]); + while (*p && isspace (*p)) + { + ++p; + } + + if (1 == sscanf (p, "%*s %d%1s", &port, &c)) return true; + + // check for [ip/host]:[port] format + while (*p && !isspace (*p) && ':' != *p) + { + ++p; + } + if ('\0' == *p) return true; // case without a port number + if (isspace (*p)) return false; + if (1 == sscanf (p, ":%d%1s", &port, &c)) return true; + + return false; +} + +/*! Says whether a \a string is a valid SCSI device entry. + * + * Valid SCSI device entries have the following format: + * - \c scsi[ vendor[ model]] + * + * Vendor and model specifications are case-insensitive. + * + * \todo Consider support for absolute path names to a device. + */ +static bool +_cfg_is_valid_scsi_entry (const char *string) +{ + const char *p = string; + + require (string); + + if (0 == strcmp_c (string, _cfg_key[CFG_KEY_SCSI])) + return true; + + if (0 != strncmp_c (string, _cfg_key[CFG_KEY_SCSI], + strlen (_cfg_key[CFG_KEY_SCSI])) + || !isspace (string[strlen(_cfg_key[CFG_KEY_SCSI])])) + return false; + + p += strlen (_cfg_key[CFG_KEY_SCSI]); + while (*p && isspace (*p)) + { + ++p; + } + while (*p && !isspace (*p)) + { + ++p; + } + if (!*p) return true; /* vendor only */ + + while (*p && isspace (*p)) + { + ++p; + } + while (*p && !isspace (*p)) + { + ++p; + } + if (!*p) return true; /* vendor model specification */ + + return false; /* we have trailing cruft */ +} + +/*! Decides whether a \a string constitutes a valid USB entry. + * + * Valid USB entries have the following format: + * - \c usb[ vendor_id prodoct_id] + * + * Both IDs, if present, shall be \c 0x or \c 0X prefixed sequences + * of one to four hexadecimal digits. Case of the digits \c A thru + * \c F is irrelevant and may be mixed, even within a single ID. + * + * \bug Does not catch IDs without intervening whitespace. + */ +static bool +_cfg_is_valid_usb_entry (const char *string) +{ + unsigned int vendor; + unsigned int product; + char x[] = "x"; /* matches 'x' or 'X' */ + char c = '\0'; /* matches trailing content */ + + require (string); + + if (0 == strcmp_c (string, _cfg_key[CFG_KEY_USB])) + return true; + + if (0 != strncmp_c (string, _cfg_key[CFG_KEY_USB], + strlen (_cfg_key[CFG_KEY_USB])) + || !isspace (string[strlen(_cfg_key[CFG_KEY_USB])])) + return false; + + return (4 == sscanf (string, "%*s 0%1[xX]%4x 0%1[xX]%4x%1s", + x, &vendor, x, &product, &c)); +} + +/*! Decides whether a \a string constitutes a valid interpreter entry. + * + * Valid interpreter entries have the following format: + * - \c interpreter usb vendor_id prodoct_id library[ firmware-file] + * + * Both IDs, if present, shall be \c 0x or \c 0X prefixed sequences + * of one to four hexadecimal digits. Case of the digits \c A thru + * \c F is irrelevant and may be mixed, even within a single ID. + * + * \bug Does not catch IDs without intervening whitespace. + */ +static bool +_cfg_is_valid_interpreter_entry (const char *string) +{ + const char *p1 = string; + const char *p2 = _cfg_key[CFG_KEY_INTERPRETER]; + + unsigned int vendor; + unsigned int product; + char x[] = "x"; /* matches 'x' or 'X' */ + char c1 = '\0'; /* matches required whitespace */ + char c2 = '\0'; /* matches start of library-name */ + + require (string); + + if (0 != strncmp_c (p1, p2, strlen (p2))) + return false; + + p1 += strlen (p2); + while (*p1 && isspace (*p1)) + { + ++p1; + } + + p2 = _cfg_key[CFG_KEY_USB]; + if (0 != strncmp_c (p1, p2, strlen (p2)) + || !isspace (p1[strlen(p2)])) + return false; + + return (6 == sscanf (string, "%*s %*s 0%1[xX]%4x 0%1[xX]%4x%c %c", + x, &vendor, x, &product, &c1, &c2) + && isspace (c1)); +} + +/*! Says whether \a string is a valid generic key/value entry. + * + * Valid entries have the following format: + * - \c <key> <value> + */ +static bool +_cfg_is_valid_key_value_entry (cfg_key_id_type key_id, const char *string) +{ + require (string); + + if (0 != strncmp_c (string, _cfg_key[key_id], + strlen (_cfg_key[key_id])) + || !isspace (string[strlen(_cfg_key[key_id])])) + return false; + + return true; +} + +/*! Says whether \a string is a valid FS blacklist entry. + * + * Valid blacklist entries have the following format: + * - \c fs-blacklist fw_name + */ +static bool +_cfg_is_valid_fs_blacklist_entry (const char *string) +{ + return _cfg_is_valid_key_value_entry (CFG_KEY_FS_BLACKLIST, string); +} + +/*! Says whether \a string is a valid option entry. + * + * Valid option entries have the following format: + * - \c option option_name + */ +static bool +_cfg_is_valid_option_entry (const char *string) +{ + return _cfg_is_valid_key_value_entry (CFG_KEY_OPTION, string); +} + +/*! Does \e not register a \a string at all. + + This function should never be called at run-time. It is solely + meant as a place holder for use in the \c _cfg_register array. + + Always returns \c false. + */ +static bool +_cfg_register_no_op (const char *string) +{ + require (string); + + err_minor ("internal error: '%s'", string); + return false; +} + +static bool +_cfg_register_net_entry (const char *string) +{ + cfg_net_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_NET]) + _cfg->seen[CFG_KEY_NET] = list_create (); + if (!_cfg->seen[CFG_KEY_NET]) return false; + + info = t_malloc (1, cfg_net_info); + if (info) + { + char *spec = NULL; + int port = 0; + + string += strlen (_cfg_key[CFG_KEY_NET]); + while (++string && isspace (*string)) + { + /* condition does all the processing */ + } + spec = strdup (string); + + if (1 == sscanf (spec, "%*s %d", &port)) + { + char *colon; + char *p = spec; + while (*p && !isspace (*p)) + { + ++p; + } + *p = ':'; + colon = p; + ++p; + while (*p && isspace (*p)) + { + ++p; + } + memmove (colon + 1, p, strlen (p) + 1); + } + + if (list_append (_cfg->seen[CFG_KEY_NET], info)) + { + info->spec = spec; + log_info ("registered '%s'", info->spec); + } + else + { + delete (spec); + delete (info); + } + } + + return (NULL != info); +} + +/*! Attemps to register supported SCSI device information. + */ +static bool +_cfg_register_scsi_entry (const char *string) +{ + cfg_scsi_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_SCSI]) + _cfg->seen[CFG_KEY_SCSI] = list_create (); + if (!_cfg->seen[CFG_KEY_SCSI]) return false; + + info = t_malloc (1, cfg_scsi_info); + if (info) + { + char *vendor = NULL; + char *model = NULL; + + sscanf (string, "%*s %as %as", &vendor, &model); + + if (list_append (_cfg->seen[CFG_KEY_SCSI], info)) + { + info->vendor = vendor; + info->model = model; + log_info ("registered '%s'", string); + } + else + { + delete (vendor); + delete (model); + delete (info); + } + } + + return (NULL != info); +} + +/*! Attempts to register information for a USB entry. + * + * Relies on the assumption that the \a string has been validated and + * returns \c true if successfull. When not successful, this will be + * caused by lack of memory and the function returns \c false. + */ +static bool +_cfg_register_usb_entry (const char *string) +{ + cfg_usb_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_USB]) + _cfg->seen[CFG_KEY_USB] = list_create (); + if (!_cfg->seen[CFG_KEY_USB]) return false; + + info = t_malloc (1, cfg_usb_info); + if (info) + { + unsigned int vendor; + unsigned int product; + + sscanf (string, "%*s %x %x", &vendor, &product); + + if (list_append (_cfg->seen[CFG_KEY_USB], info)) + { + info->vendor = vendor; + info->product = product; + log_info ("registered '%s'", string); + } + else + { + delete (info); + } + } + + return (NULL != info); +} + +/*! Tries to register information for an interpreter entry. + * + * \todo Check for existence of the \a library and \a firmware file. + */ +static bool +_cfg_register_interpreter_entry (const char *string) +{ + cfg_interpreter_info *info = NULL; + + require (string); + + if (!_cfg->seen[CFG_KEY_INTERPRETER]) + _cfg->seen[CFG_KEY_INTERPRETER] = list_create (); + if (!_cfg->seen[CFG_KEY_INTERPRETER]) return false; + + info = t_malloc (1, cfg_interpreter_info); + if (info) + { + unsigned int vendor; + unsigned int product; + char *library = NULL; + char *firmware = NULL; + + sscanf (string, "%*s %*s %x %x %as %as", + &vendor, &product, &library, &firmware); + + if (library && _cfg_have_interpreter (library, firmware) + && list_append (_cfg->seen[CFG_KEY_INTERPRETER], info)) + { + info->vendor = vendor; + info->product = product; + info->library = library; + info->firmware = firmware; + log_info ("registered '%s'", string); + } + else + { + delete (library); + delete (firmware); + delete (info); + } + } + return (NULL != info); +} + +/*! Registers information for a generic key/value entry + */ +static bool +_cfg_register_key_value_entry (cfg_key_id_type key_id, const char *string) +{ + char *value = NULL; + + require (string); + + if (!_cfg->seen[key_id]) + _cfg->seen[key_id] = list_create (); + if (!_cfg->seen[key_id]) return false; + + string += strlen (_cfg_key[key_id]); + while (++string && isspace (*string)) + { + /* condition does all the processing */ + } + value = strdup (string); + + if (!list_append (_cfg->seen[key_id], value)) + { + delete (value); + } + if (NULL != value) + { + log_info ("registered '%s %s'", _cfg_key[key_id], value); + } + return (NULL != value); +} + +/*! Registers information for an FS command blacklist entry + */ +static bool +_cfg_register_fs_blacklist_entry (const char *string) +{ + return _cfg_register_key_value_entry (CFG_KEY_FS_BLACKLIST, string); +} + +/*! Registers information for an option entry + */ +static bool +_cfg_register_option_entry (const char *string) +{ + int i = 0; + bool found = false; + const char* value = NULL; + + value = string + strlen (_cfg_key[CFG_KEY_OPTION]); + while (++value && isspace (*value)) + { + /* condition does all the processing */ + } + + for (i=0; i<num_of (_opt_vals); ++i) + { + if (0 == strcmp_c (value, _opt_vals[i])) + { + found = true; + break; + } + } + if (!found) + { + log_info ("unknown option: '%s'", value); + return false; + } + return _cfg_register_key_value_entry (CFG_KEY_OPTION, string); +} + +/*! + */ +static void +_cfg_probe_no_op (list *dev_list) +{ + return; +} + +/* Reuse the SCSI attach function because it does all we need to do, + at least for as long as it caters to the sanei_scsi API. + */ +#define _cfg_net_attach _cfg_scsi_attach + +/*! + */ +static void +_cfg_probe_net (list *dev_list) +{ + char* buf = NULL; + char* rbuf = NULL; + + size_t size = 0; + + ssize_t n = 0; + uint16_t id = 0; + uint8_t status; + + void* net = NULL; + int sock = -1; + + int i = 0; + char* cp; + + list *registry = _cfg->seen[CFG_KEY_NET]; + cfg_net_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + net = net_init (NULL, NULL); + if (net) sock = net_get_sock (net); + + if (!net || 0 > sock) + { + cfg_set (_cfg, CFG_KEY_NET, false); + return; + } + + /* construct string from list of scanners */ + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + /* +1 for separator */ + size += strlen (info->spec) + 1; + } + registry->cur = cur; + + buf = t_malloc (size+1, char); /* +1 for NULL termination character */ + if (!buf) + { + cfg_set (_cfg, CFG_KEY_NET, false); + return; + } + memset (buf, 0, size+1); + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + strcat (buf, info->spec); + strcat (buf, "\n"); + } + registry->cur = cur; + + log_info ("Probe network:\n%s", buf); + + /* send string of NULL separated scanner ips to the network plugin */ + for (i=0; i<size; ++i) if ('\n' == buf[i]) buf[i] = '\0'; + n = ipc_send (sock, 0, TYPE_LIST, size, buf); + delete (buf); + + if (n != size) + { + log_info ("Communication error occurred. Disabling network plugin."); + cfg_set (_cfg, CFG_KEY_NET, false); + return; + } + + /* receive a string of available network scanners */ + n = -1; + int tries = 3; + while (0 > n && 0 < tries) + { + n = ipc_recv (sock, &id, &status, (void **) &rbuf); + --tries; + } + + /* bail if no network scanners were found or something bad happened */ + if (0 >= n || 0 == strlen (rbuf) || status != STATUS_OK) + { + log_info ("No network scanners detected. Disabling network plugin."); + cfg_set (_cfg, CFG_KEY_NET, false); + delete (rbuf); + return; + } + + /* process the scanner listing string */ + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_NET]; + + cp = rbuf; + for (i=0; i<n; ++i) + { + if ('\0' == rbuf[i]) + { + log_info ("Detected network scanner: %s", cp); + _cfg_net_attach (cp); + cp = rbuf + i + 1; + } + } + + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + + delete (rbuf); + + return; +} + +/*! + */ +static void +_cfg_probe_scsi (list *dev_list) +{ + list *registry = _cfg->seen[CFG_KEY_SCSI]; + cfg_scsi_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_SCSI]; + sanei_scsi_find_devices (info->vendor, info->model, NULL, + -1, -1, -1, -1, _cfg_scsi_attach); + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + } + registry->cur = cur; + + return; +} + +static void +_cfg_probe_usb (list *dev_list) +{ + list *registry = _cfg->seen[CFG_KEY_USB]; + cfg_usb_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_USB]; + sanei_usb_find_devices (info->vendor, info->product, _cfg_usb_attach); + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + } + registry->cur = cur; + + return; +} + +/*! + */ +static void +_cfg_probe_interpreter (list *dev_list) +{ + list *registry = _cfg->seen[CFG_KEY_INTERPRETER]; + cfg_interpreter_info *info = NULL; + list_entry *cur; + + require (dev_list); + if (!registry) return; + + cur = registry->cur; + list_reset (registry); + while ((info = list_next (registry))) + { + _cfg_dev_list = dev_list; + _cfg_dev_key = _cfg_key[CFG_KEY_INTERPRETER]; + sanei_usb_find_devices (info->vendor, info->product, _cfg_usb_attach); + _cfg_dev_key = NULL; + _cfg_dev_list = NULL; + } + registry->cur = cur; + + return; +} + +static SANE_Status +_cfg_scsi_attach (SANE_String_Const dev_name) +{ + SANE_String name = NULL; + size_t len_name = (strlen (_cfg_dev_key) + strlen (DEV_NAME_SEP_STR) + + strlen (dev_name) + 1); + + name = t_malloc (len_name, SANE_Char); + if (name) + { + SANE_Status s = SANE_STATUS_GOOD; + + strcpy (name, _cfg_dev_key); + strcat (name, DEV_NAME_SEP_STR); + strcat (name, dev_name); + + s = _cfg_attach (name, _cfg_dev_list); + if (SANE_STATUS_NO_MEM == s) + { + delete (name); + } + return s; + } + return SANE_STATUS_NO_MEM; +} + +static SANE_Status +_cfg_usb_attach (SANE_String_Const dev_name) +{ + const char *sanei_usb_prefix = "libusb:"; + + SANE_String name = NULL; + size_t len_name = (strlen (_cfg_dev_key) + strlen (DEV_NAME_SEP_STR) + + strlen (dev_name) + 1); + + if (0 == strncmp_c (dev_name, sanei_usb_prefix, strlen (sanei_usb_prefix))) + { + len_name -= strlen (sanei_usb_prefix); + dev_name += strlen (sanei_usb_prefix); + } + + name = t_malloc (len_name, SANE_Char); + if (name) + { + SANE_Status s = SANE_STATUS_GOOD; + + strcpy (name, _cfg_dev_key); + strcat (name, DEV_NAME_SEP_STR); + strcat (name, dev_name); + + s = _cfg_attach (name, _cfg_dev_list); + if (SANE_STATUS_NO_MEM == s) + { + delete (name); + } + return s; + } + return SANE_STATUS_NO_MEM; +} + +/*! Creates a SANE_Device and attaches it to the \a dev_list. + * + * While perhaps not particularly user-friendly, it is perfectly okay + * to attach a SANE_Device with the vendor, model and type fields set + * to something that does not exactly correspond with the device that + * is dangling on the other end of the connection. + * + * We may not be able to get reliable information for several reasons, + * not in the least because of (intermittent?) failure to communicate + * with the device. + * + * Returns SANE_STATUS_NO_MEM if a SANE_Device could not be created + * and added to the \a dev_list, SANE_STATUS_GOOD otherwise. Note + * that in the latter case only the name field is guaranteed to be + * not \c NULL. + */ +static SANE_Status +_cfg_attach (SANE_String_Const dev_name, list *dev_list) +{ + SANE_Device *dev = t_malloc (1, SANE_Device); + + require (dev_name); + + if (!dev || !list_append (dev_list, dev)) + { + delete (dev); + return SANE_STATUS_NO_MEM; + } + + dev->name = dev_name; + + dev->vendor = _cfg_get_vendor (dev_name); + dev->model = _cfg_get_model (dev_name); + dev->type = _cfg_get_type (dev_name); + + return SANE_STATUS_GOOD; +} + +/*! Returns a best effort vendor name for a device. + * + * \todo Remove hard-coding (as soon as we support non-Epson + * devices). + */ +static SANE_String_Const +_cfg_get_vendor (SANE_String_Const dev_name) +{ + return strdup ("Epson"); +} + +/*! Returns a best effort model name for a device. + */ +static SANE_String_Const +_cfg_get_model (SANE_String_Const dev_name) +{ + SANE_String_Const model = NULL; + SANE_Status status = SANE_STATUS_GOOD; + + char *fw_name = NULL; + channel *ch = NULL; + + require (dev_name); + + ch = channel_create (dev_name, &status); + if (!ch || SANE_STATUS_GOOD != status) + { + err_minor ("%s", sane_strstatus (status)); + } + else + { + ch->open (ch, &status); + if (SANE_STATUS_GOOD == status) + { + fw_name = get_fw_name (ch); + } + ch->close (ch, NULL); + ch = ch->dtor (ch); + } + + log_info ("F/W name: '%s'", fw_name); + + model = model_info_cache_get_model (fw_name); + delete (fw_name); + + return model; +} + +/*! Returns a best effort type description for a device. + * + * \todo Figure out how to deal with the predefined strings in the + * face of devices that have a flatbed and ADF and/or TPU. + * Then there is also the various ideas people have about + * multi-function peripherals. Are these SPC's, all-in-ones, + * MFPPs or even plain scanners with both a flatbed and ADF? + */ +static SANE_String_Const +_cfg_get_type (SANE_String_Const dev_name) +{ + return strdup ("flatbed scanner"); +} + +/*! Divines whether a certain interpreter is usable. + */ +static bool +_cfg_have_interpreter (const char *library, const char *firmware) +{ + require (library); + + return true; +} + + +static void +_cfg_net_dtor (void *self) +{ + cfg_net_info *p = self; + + if (!p) return; + + const_delete (p->spec, char *); + delete (p); +} + +static void +_cfg_scsi_dtor (void *self) +{ + cfg_scsi_info *p = self; + + if (!p) return; + + const_delete (p->vendor, char *); + const_delete (p->model , char *); + delete (p); +} + +static void +_cfg_interpreter_dtor (void *self) +{ + cfg_interpreter_info *p = self; + + if (!p) return; + + const_delete (p->library , char *); + const_delete (p->firmware, char *); + delete (p); +} |