/* 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 . * * * 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 #endif #include "cfg-obj.h" #include #include #include #include #include #include #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 */ 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; iseen[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 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; iseen[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); }