/* sane-desc.c -- generate list of supported SANE devices Copyright (C) 2002-2006 Henning Meier-Geinitz Copyright (C) 2004 Jose Gato (XML output) Copyright (C) 2006 Mattias Ellert (plist output) Copyright (C) 2009 Dr. Ing. Dieter Jurzitza Copyright (C) 2013 Tom Gundersen (hwdb output) 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. */ #include <../include/sane/config.h> #include "lgetopt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../include/sane/sane.h" #include "../include/sane/sanei.h" #include "../include/sane/sanei_config.h" #define SANE_DESC_VERSION "3.6" #define MAN_PAGE_LINK "man/%s.5.html" #define COLOR_MINIMAL "\"#B00000\"" #define COLOR_BASIC "\"#FF9000\"" #define COLOR_GOOD "\"#90B000\"" #define COLOR_COMPLETE "\"#007000\"" #define COLOR_UNTESTED "\"#0000B0\"" #define COLOR_UNSUPPORTED "\"#F00000\"" #define COLOR_NEW "\"#F00000\"" #define COLOR_UNKNOWN "\"#000000\"" #define DEVMODE "0664" #define DEVOWNER "root" #define DEVGROUP "scanner" #ifndef PATH_MAX # define PATH_MAX 1024 #endif #define DBG_ERR current_debug_level = 0; debug_call #define DBG_WARN current_debug_level = 1; debug_call #define DBG_INFO current_debug_level = 2; debug_call #define DBG_DBG current_debug_level = 3; debug_call typedef enum output_mode { output_mode_ascii = 0, output_mode_xml, output_mode_html_backends, output_mode_html_backends_split, output_mode_html_mfgs, output_mode_statistics, output_mode_usermap, output_mode_db, output_mode_udev, output_mode_udevacl, output_mode_udevhwdb, output_mode_hwdb, output_mode_plist, output_mode_hal, output_mode_halnew } output_mode; typedef enum parameter_type { param_none = 0, param_string, param_two_strings, param_three_strings } parameter_type; typedef enum status_entry { status_unknown, status_unsupported, status_untested, status_minimal, status_basic, status_good, status_complete } status_entry; typedef enum device_type { type_unknown, type_scanner, type_stillcam, type_vidcam, type_meta, type_api } device_type; typedef enum level { level_backend, level_mfg, level_model, level_desc } level; typedef struct url_entry { struct url_entry *next; char *name; } url_entry; typedef struct model_entry { struct model_entry *next; char *name; char *interface; struct url_entry *url; char *comment; enum status_entry status; char *usb_vendor_id; char *usb_product_id; SANE_Bool ignore_usb_id; char *scsi_vendor_id; char *scsi_product_id; SANE_Bool scsi_is_processor; } model_entry; typedef struct desc_entry { struct desc_entry *next; char *desc; struct url_entry *url; char *comment; } desc_entry; typedef struct mfg_entry { struct mfg_entry *next; char *name; struct url_entry *url; char *comment; struct model_entry *model; } mfg_entry; typedef struct type_entry { struct type_entry *next; enum device_type type; struct desc_entry *desc; struct mfg_entry *mfg; } type_entry; typedef struct backend_entry { struct backend_entry *next; char *name; char *version; char *manpage; struct url_entry *url; char *comment; struct type_entry *type; SANE_Bool new; } backend_entry; typedef struct model_record_entry { struct model_record_entry *next; char *name; char *interface; struct url_entry *url; char *comment; enum status_entry status; char *usb_vendor_id; char *usb_product_id; char *scsi_vendor_id; char *scsi_product_id; SANE_Bool scsi_is_processor; struct backend_entry *be; } model_record_entry; typedef struct mfg_record_entry { struct mfg_record_entry *next; char *name; char *comment; struct url_entry *url; struct model_record_entry *model_record; } mfg_record_entry; typedef int statistics_type [status_complete + 1]; typedef struct manufacturer_model_type { struct manufacturer_model_type * next; char *name; } manufacturer_model_type; typedef struct usbid_type { struct usbid_type * next; char *usb_vendor_id; char *usb_product_id; struct manufacturer_model_type *name; } usbid_type; typedef struct scsiid_type { struct scsiid_type * next; char *scsi_vendor_id; char *scsi_product_id; SANE_Bool is_processor; struct manufacturer_model_type *name; } scsiid_type; static char *program_name; static int debug = 0; static int current_debug_level = 0; static char *search_dir_spec = 0; static backend_entry *first_backend = 0; static enum output_mode mode = output_mode_ascii; static char *title = 0; static char *intro = 0; static SANE_String desc_name = 0; static const char *status_name[] = {"Unknown", "Unsupported", "Untested", "Minimal", "Basic", "Good", "Complete"}; static const char *device_type_name[] = {"Unknown", "Scanners", "Still cameras", "Video Cameras", "Meta backends", "APIs"}; static const char *device_type_aname[] = {"UKNOWN", "SCANNERS", "STILL", "VIDEO", "META", "API"}; static const char *status_color[] = {COLOR_UNKNOWN, COLOR_UNSUPPORTED, COLOR_UNTESTED, COLOR_MINIMAL, COLOR_BASIC, COLOR_GOOD, COLOR_COMPLETE}; static void debug_call (const char *fmt, ...) { va_list ap; char *level_txt; va_start (ap, fmt); if (debug >= current_debug_level) { /* print to stderr */ switch (current_debug_level) { case 0: level_txt = "ERROR:"; break; case 1: level_txt = "Warning:"; break; case 2: level_txt = "Info:"; break; default: level_txt = ""; break; } if (desc_name) fprintf (stderr, "%s: %8s ", desc_name, level_txt); else fprintf (stderr, "[%s] %8s ", program_name, level_txt); vfprintf (stderr, fmt, ap); } va_end (ap); } static void print_usage (char *program_name) { printf ("Usage: %s [-s dir] [-m mode] [-d level] [-h] [-V]\n", program_name); printf (" -s|--search-dir dir " "Specify the directory that contains .desc files\n" " " "(multiple directories can be concatenated by \":\")\n"); printf (" -m|--mode mode " "Output mode (ascii, html-backends-split, html-mfgs,\n" " xml, statistics, usermap, db, udev, udev+acl, udev+hwdb, hwdb, plist, hal, hal-new)\n"); printf (" -t|--title \"title\" The title used for HTML pages\n"); printf (" -i|--intro \"intro\" A short description of the " "contents of the page\n"); printf (" -d|--debug-level level Specify debug level (0-3)\n"); printf (" -h|--help Print help message\n"); printf (" -V|--version Print version information\n"); printf ("Report bugs to \n"); } static void print_version (void) { printf ("sane-desc %s (%s)\n", SANE_DESC_VERSION, PACKAGE_STRING); printf ("Copyright (C) 2002-2006 Henning Meier-Geinitz " "\n" "sane-desc comes with NO WARRANTY, to the extent permitted by " "law.\n" "You may redistribute copies of sane-desc under the terms of the " "GNU General\n" "Public License.\n" "For more information about these matters, see the file named " "COPYING.\n"); } static SANE_Bool get_options (int argc, char **argv) { int longindex; int opt; static struct option desc_options[] = { {"search-dir", required_argument, NULL, 's'}, {"mode", required_argument, NULL, 'm'}, {"title", required_argument, NULL, 't'}, {"intro", required_argument, NULL, 'i'}, {"debug-level", required_argument, NULL, 'd'}, {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'V'}, {0, 0, 0, 0} }; while ((opt = getopt_long (argc, argv, "s:m:t:i:d:hV", desc_options, &longindex)) != -1) { switch (opt) { case 'h': print_usage (argv[0]); exit (0); case 'V': print_version (); exit (0); case 's': search_dir_spec = strdup (optarg); DBG_INFO ("setting search directory to `%s'\n", search_dir_spec); break; case 'm': if (strcmp (optarg, "ascii") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_ascii; } else if (strcmp (optarg, "xml") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_xml; } else if (strcmp (optarg, "html-backends-split") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_html_backends_split; } else if (strcmp (optarg, "html-mfgs") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_html_mfgs; } else if (strcmp (optarg, "statistics") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_statistics; } else if (strcmp (optarg, "usermap") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_usermap; } else if (strcmp (optarg, "db") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_db; } else if (strcmp (optarg, "udev") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_udev; } else if (strcmp (optarg, "udev+acl") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_udevacl; } else if (strcmp (optarg, "udev+hwdb") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_udevhwdb; } else if (strcmp (optarg, "hwdb") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_hwdb; } else if (strcmp (optarg, "plist") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_plist; } else if (strcmp (optarg, "hal") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_hal; } else if (strcmp (optarg, "hal-new") == 0) { DBG_INFO ("Output mode: %s\n", optarg); mode = output_mode_halnew; } else { DBG_ERR ("Unknown output mode: %s\n", optarg); exit (1); } break; case 't': title = optarg; DBG_INFO ("setting title to `%s'\n", optarg); break; case 'i': intro = optarg; DBG_INFO ("setting intro to `%s'\n", optarg); break; case 'd': debug = atoi (optarg); DBG_INFO ("setting debug level to %d\n", debug); break; case '?': DBG_ERR ("unknown option (use -h for help)\n"); return SANE_FALSE; case ':': DBG_ERR ("missing parameter (use -h for help)\n"); return SANE_FALSE; default: DBG_ERR ("missing option (use -h for help)\n"); return SANE_FALSE; } } if (!search_dir_spec) search_dir_spec = "."; return SANE_TRUE; } static int char_compare (char char1, char char2) { char1 = toupper (char1); char2 = toupper (char2); if (char1 < char2) return -1; else if (char1 > char2) return 1; else return 0; } static int num_compare (char *num_string1, char *num_string2) { int num1 = atoi (num_string1); int num2 = atoi (num_string2); if (num1 < num2) return -1; else if (num1 > num2) return 1; else return 0; } /* Compare two strings, try to sort numbers correctly (600 < 1200) */ static int string_compare (char *string1, char *string2) { int count = 0; int compare = 0; if (!string1) { if (!string2) return 0; else return 1; } else if (!string2) return -1; while (string1[count] && string2[count]) { if (isdigit (string1[count]) && isdigit (string2[count])) compare = num_compare (&string1[count], &string2[count]); else compare = char_compare (string1[count], string2[count]); if (compare != 0) return compare; count++; } return char_compare (string1[count], string2[count]); } /* Add URLs to the end of the list if they are unique */ static url_entry * update_url_list (url_entry * first_url, char *new_url) { url_entry *url = first_url; SANE_Bool found = SANE_FALSE; while (url && url->name) { if (string_compare (url->name, new_url) == 0) found = SANE_TRUE; url = url->next; } if (found) return first_url; url = first_url; if (url) { while (url->next) url = url->next; url->next = calloc (1, sizeof (url_entry)); url = url->next; } else { first_url = calloc (1, sizeof (url_entry)); url = first_url; } if (!url) { DBG_ERR ("update_url_list: couldn't calloc url_entry\n"); exit (1); } url->name = new_url; return first_url; } /* Get the next token, ignoring escaped quotation marks */ static const char * get_token (const char *str, char **string_const) { const char *start; size_t len; str = sanei_config_skip_whitespace (str); if (*str == '"') { start = ++str; while (*str && (*str != '"' || *(str - 1) == '\\')) ++str; len = str - start; if (*str == '"') ++str; else start = 0; /* final double quote is missing */ } else { start = str; while (*str && !isspace (*str)) ++str; len = str - start; } if (start) *string_const = strndup (start, len); else *string_const = NULL; return str; } /* Checks a line for a keyword token and determines keyword/string argument */ static SANE_Status read_keyword (SANE_String line, SANE_String keyword_token, parameter_type p_type, void *argument) { SANE_String_Const cp; SANE_Char *word; word = 0; cp = get_token (line, &word); if (!word) { DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } if (strcmp (word, keyword_token) != 0) { free(word); return SANE_STATUS_INVAL; } free (word); word = 0; switch (p_type) { case param_none: return SANE_STATUS_GOOD; case param_string: { char *pos; cp = get_token (cp, &word); if (!word) { DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_DBG ("read_keyword: set entry `%s' to `%s'\n", keyword_token, word); *(SANE_String *) argument = strdup (word); break; } case param_two_strings: { char *pos; char **strings = malloc (2 * sizeof (SANE_String)); cp = get_token (cp, &word); if (!word) { free(strings); DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_INFO ("read_keyword: set first entry of `%s' to `%s'\n", keyword_token, word); strings[0] = strdup (word); if (word) free (word); cp = get_token (cp, &word); if (!word) { free(strings); DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_INFO ("read_keyword: set second entry of `%s' to `%s'\n", keyword_token, word); strings[1] = strdup (word); * (SANE_String **) argument = strings; break; } case param_three_strings: { char *pos; char **strings = malloc (3 * sizeof (SANE_String)); cp = get_token (cp, &word); if (!word) { free(strings); DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_INFO ("read_keyword: set first entry of `%s' to `%s'\n", keyword_token, word); strings[0] = strdup (word); if (word) free (word); cp = get_token (cp, &word); if (!word) { free(strings); DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_INFO ("read_keyword: set second entry of `%s' to `%s'\n", keyword_token, word); strings[1] = strdup (word); if (word) free (word); cp = get_token (cp, &word); if (!word) { free(strings); DBG_ERR ("read_keyword: missing quotation mark: %s\n", line); return SANE_STATUS_INVAL; } /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_INFO ("read_keyword: set third entry of `%s' to `%s'\n", keyword_token, word); strings[2] = strdup (word); * (SANE_String **) argument = strings; break; } default: DBG_ERR ("read_keyword: unknown param_type %d\n", p_type); return SANE_STATUS_INVAL; } if (word) free (word); word = 0; return SANE_STATUS_GOOD; } /* Check for a all-lowercase 4-digit hex number (e.g. 0x1234) */ static SANE_Bool check_hex (SANE_String string) { unsigned int i; if (strlen (string) != 6) return SANE_FALSE; if (strncmp (string, "0x", 2) != 0) return SANE_FALSE; for (i = 0; i < strlen (string); i++) { if (isupper (string[i])) return SANE_FALSE; } for (i = 2; i < strlen (string); i++) { if (!isxdigit (string[i])) return SANE_FALSE; } return SANE_TRUE; } /* Read and interprete the .desc files */ static SANE_Bool read_files (void) { struct stat stat_buf; DIR *dir; struct dirent *dir_entry; FILE *fp; char file_name[PATH_MAX]; SANE_Char line[4096], *word; SANE_String_Const cp; backend_entry *current_backend = 0; type_entry *current_type = 0; mfg_entry *current_mfg = 0; model_entry *current_model = 0; enum level current_level = level_backend; char *search_dir = search_dir_spec, *end = 0; DBG_INFO ("looking for .desc files in `%s'\n", search_dir_spec); while (search_dir && search_dir[0]) { end = strchr (search_dir, ':'); if (end) end[0] = '\0'; DBG_INFO ("reading directory `%s'\n", search_dir); if (stat (search_dir, &stat_buf) < 0) { DBG_ERR ("cannot stat `%s' (%s)\n", search_dir, strerror (errno)); return SANE_FALSE; } if (!S_ISDIR (stat_buf.st_mode)) { DBG_ERR ("`%s' is not a directory\n", search_dir); return SANE_FALSE; } if ((dir = opendir (search_dir)) == 0) { DBG_ERR ("cannot read directory `%s' (%s)\n", search_dir, strerror (errno)); return SANE_FALSE; } while ((dir_entry = readdir (dir)) != NULL) { if (strlen (dir_entry->d_name) > 5 && strcmp (dir_entry->d_name + strlen (dir_entry->d_name) - 5, ".desc") == 0) { if (strlen (search_dir) + strlen (dir_entry->d_name) + 1 + 1 > PATH_MAX) { DBG_ERR ("filename too long\n"); return SANE_FALSE; } sprintf (file_name, "%s/%s", search_dir, dir_entry->d_name); DBG_INFO ("-> reading desc file: %s\n", file_name); fp = fopen (file_name, "r"); if (!fp) { DBG_ERR ("can't open desc file: %s (%s)\n", file_name, strerror (errno)); return SANE_FALSE; } /* now we check if everything is ok with the previous backend before we read the new one */ if (current_backend) { type_entry *current_type = current_backend->type; int no_usbids = 0; int no_interface = 0; int no_status = 0; while (current_type) { if (current_type->type == type_scanner || current_type->type == type_stillcam || current_type->type == type_vidcam) { mfg_entry *current_mfg = current_type->mfg; while (current_mfg) { model_entry *current_model = current_mfg->model; while (current_model) { if (current_model->status == status_unknown) { DBG_INFO ("Backend `%s': `%s' `%s' does not have a status\n", current_backend->name, current_mfg->name, current_model->name); no_status++; } if (!current_model->interface) { DBG_INFO ("Backend `%s': `%s' `%s' does not have an interface\n", current_backend->name, current_mfg->name, current_model->name); no_interface++; } else if (strstr (current_model->interface, "USB")) { if ((!current_model->usb_vendor_id || !current_model->usb_product_id) && !current_model->ignore_usb_id) { DBG_INFO ("`%s' seems to provide a USB device " "without :usbid (%s %s)\n", current_backend->name, current_mfg->name, current_model->name); no_usbids++; } } current_model = current_model->next; } current_mfg = current_mfg->next; } } current_type = current_type->next; } if (no_status) { DBG_WARN ("Backend `%s': %d devices without :status\n", current_backend->name, no_status); } if (no_interface) { DBG_WARN ("Backend `%s': %d devices without :interface\n", current_backend->name, no_interface); } if (no_usbids) { DBG_WARN ("Backend `%s': %d USB devices without :usbid\n", current_backend->name, no_usbids); } } desc_name = dir_entry->d_name; current_backend = 0; current_type = 0; current_mfg = 0; current_model = 0; while (sanei_config_read (line, sizeof (line), fp)) { char *string_entry = 0; char **two_string_entry; char **three_string_entry; word = 0; cp = get_token (line, &word); if (!word || cp == line) { DBG_DBG ("ignoring empty line\n"); if (word) free (word); word = 0; continue; } if (word[0] == ';') { DBG_DBG ("ignoring comment line\n"); free (word); word = 0; continue; } DBG_DBG ("line: %s\n", line); if (read_keyword (line, ":backend", param_string, &string_entry) == SANE_STATUS_GOOD) { backend_entry *be = first_backend, *prev_be = 0, *new_be = 0; DBG_INFO ("creating backend entry `%s'\n", string_entry); new_be = calloc (1, sizeof (backend_entry)); if (!new_be) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } new_be->name = string_entry; new_be->new = SANE_FALSE; if (!be) { first_backend = new_be; be = new_be; } else { while (be) { int compare = string_compare (new_be->name, be->name); if (compare <= 0) { backend_entry *be_tmp = be; be = new_be; be->next = be_tmp; if (!prev_be) first_backend = be; else prev_be->next = be; break; } prev_be = be; be = be->next; } if (!be) /* last entry */ { prev_be->next = new_be; be = prev_be->next; } } current_backend = be; current_type = 0; current_mfg = 0; current_model = 0; current_level = level_backend; continue; } if (!current_backend) { DBG_ERR ("use `:backend' keyword first\n"); return SANE_FALSE; } if (read_keyword (line, ":version", param_string, &string_entry) == SANE_STATUS_GOOD) { if (current_backend->version) { DBG_WARN ("overwriting version of backend `%s' to `%s'" "(was: `%s')\n", current_backend->name, string_entry, current_backend->version, current_backend->version); } DBG_INFO ("setting version of backend `%s' to `%s'\n", current_backend->name, string_entry); current_backend->version = string_entry; continue; } if (read_keyword (line, ":status", param_string, &string_entry) == SANE_STATUS_GOOD) { switch (current_level) { case level_model: if (current_model->status != status_unknown) { DBG_WARN ("overwriting status of model `%s' (backend `%s')\n", current_model->name, current_backend->name); } if (strcmp (string_entry, ":minimal") == 0) { DBG_INFO ("setting status of model `%s' to `minimal'\n", current_model->name); current_model->status = status_minimal; } else if (strcmp (string_entry, ":basic") == 0) { DBG_INFO ("setting status of model `%s' to `basic'\n", current_model->name); current_model->status = status_basic; } else if (strcmp (string_entry, ":good") == 0) { DBG_INFO ("setting status of model `%s' to `good'\n", current_model->name); current_model->status = status_good; } else if (strcmp (string_entry, ":complete") == 0) { DBG_INFO ("setting status of model `%s' to `complete'\n", current_model->name); current_model->status = status_complete; } else if (strcmp (string_entry, ":untested") == 0) { DBG_INFO ("setting status of model `%s' to `untested'\n", current_model->name); current_model->status = status_untested; } else if (strcmp (string_entry, ":unsupported") == 0) { DBG_INFO ("setting status of model `%s' to `unsupported'\n", current_model->name); current_model->status = status_unsupported; } else { DBG_ERR ("unknown status of model `%s': `%s' (backend `%s')\n", current_model->name, string_entry, current_backend->name); current_model->status = status_untested; return SANE_FALSE; } break; default: DBG_ERR ("level %d not implemented for :status (backend `%s')\n", current_level, current_backend->name); return SANE_FALSE; } continue; } if (read_keyword (line, ":new", param_string, &string_entry) == SANE_STATUS_GOOD) { if (strcmp (string_entry, ":yes") == 0) { DBG_INFO ("backend %s is new in this SANE release\n", current_backend->name); current_backend->new = SANE_TRUE; } else if (strcmp (string_entry, ":no") == 0) { DBG_INFO ("backend %s is NOT new in this SANE release\n", current_backend->name); current_backend->new = SANE_FALSE; } else { DBG_ERR ("unknown :new parameter of backend `%s': " "`%s'\n", current_backend->name, string_entry); current_backend->new = SANE_FALSE; return SANE_FALSE; } continue; } if (read_keyword (line, ":manpage", param_string, &string_entry) == SANE_STATUS_GOOD) { if (current_backend->manpage) { DBG_WARN ("overwriting manpage of backend `%s' to `%s'" "(was: `%s')\n", current_backend->name, string_entry, current_backend->manpage); } DBG_INFO ("setting manpage of backend `%s' to `%s'\n", current_backend->name, string_entry); current_backend->manpage = string_entry; continue; } if (read_keyword (line, ":devicetype", param_string, &string_entry) == SANE_STATUS_GOOD) { type_entry *type = 0; type = current_backend->type; DBG_INFO ("adding `%s' to list of device types of backend " "`%s'\n", string_entry, current_backend->name); if (type) { while (type->next) type = type->next; type->next = calloc (1, sizeof (type_entry)); type = type->next; } else { current_backend->type = calloc (1, sizeof (type_entry)); type = current_backend->type; } type->type = type_unknown; if (strcmp (string_entry, ":scanner") == 0) { DBG_INFO ("setting device type of backend `%s' to " "scanner\n", current_backend->name); type->type = type_scanner; } else if (strcmp (string_entry, ":stillcam") == 0) { DBG_INFO ("setting device type of backend `%s' to " "still camera\n", current_backend->name); type->type = type_stillcam; } else if (strcmp (string_entry, ":vidcam") == 0) { DBG_INFO ("setting device type of backend `%s' to " "video camera\n", current_backend->name); type->type = type_vidcam; } else if (strcmp (string_entry, ":api") == 0) { DBG_INFO ("setting device type of backend `%s' to " "API\n", current_backend->name); type->type = type_api; } else if (strcmp (string_entry, ":meta") == 0) { DBG_INFO ("setting device type of backend `%s' to " "meta\n", current_backend->name); type->type = type_meta; } else { DBG_ERR ("unknown device type of backend `%s': `%s'\n", current_backend->name, string_entry); type->type = type_unknown; return SANE_FALSE; } current_type = type; current_mfg = 0; current_model = 0; continue; } if (read_keyword (line, ":desc", param_string, &string_entry) == SANE_STATUS_GOOD) { if (!current_type) { DBG_ERR ("use `:devicetype' keyword first (backend `%s')\n", current_backend->name); return SANE_FALSE; } if (current_type->type < type_meta) { DBG_ERR ("use `:desc' for `:api' and `:meta' only (backend `%s')\n", current_backend->name); return SANE_FALSE; } if (current_type->desc) { DBG_WARN ("overwriting description of device type of " "backend `%s' to `%s' (was: `%s')\n", current_backend->name, string_entry, current_type->desc); } DBG_INFO ("setting description of backend `%s' to `%s'\n", current_backend->name, string_entry); current_type->desc = calloc (1, sizeof (desc_entry)); if (!current_type->desc) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } current_type->desc->desc = string_entry; current_level = level_desc; current_mfg = 0; current_model = 0; continue; } if (read_keyword (line, ":mfg", param_string, &string_entry) == SANE_STATUS_GOOD) { mfg_entry *mfg = 0; if (!current_type) { DBG_ERR ("use `:devicetype' keyword first (backend `%s')\n", current_backend->name); return SANE_FALSE; } if (current_type->type >= type_meta) { DBG_ERR ("use `:mfg' for hardware devices only (backend `%s')\n", current_backend->name); return SANE_FALSE; } mfg = current_type->mfg; if (mfg) { while (mfg->next) mfg = mfg->next; mfg->next = calloc (1, sizeof (mfg_entry)); mfg = mfg->next; } else { current_type->mfg = calloc (1, sizeof (mfg_entry)); mfg = current_type->mfg; } if (!mfg) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } mfg->name = string_entry; DBG_INFO ("adding mfg entry %s to backend `%s'\n", string_entry, current_backend->name); current_mfg = mfg; current_model = 0; current_level = level_mfg; continue; } if (read_keyword (line, ":model", param_string, &string_entry) == SANE_STATUS_GOOD) { model_entry *model = 0; if (!current_type) { DBG_ERR ("use `:devicetype' keyword first (backend `%s')\n", current_backend->name); return SANE_FALSE; } if (current_level != level_mfg && current_level != level_model) { DBG_ERR ("use `:mfg' keyword first (backend `%s')\n", current_backend->name); return SANE_FALSE; } model = current_mfg->model; if (model) { while (model->next) model = model->next; model->next = calloc (1, sizeof (model_entry)); model = model->next; } else { current_mfg->model = calloc (1, sizeof (model_entry)); model = current_mfg->model; } if (!model) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } model->name = string_entry; model->status = status_unknown; DBG_INFO ("adding model entry %s to manufacturer `%s'\n", string_entry, current_mfg->name); current_model = model; current_level = level_model; continue; } if (read_keyword (line, ":interface", param_string, &string_entry) == SANE_STATUS_GOOD) { if (!current_model) { DBG_WARN ("ignored `%s' :interface, only allowed for " "hardware devices\n", current_backend->name); continue; } if (current_model->interface) { DBG_WARN ("overwriting `%s's interface of model " "`%s' to `%s' (was: `%s')\n", current_backend->name, current_model->name, string_entry, current_model->interface); } DBG_INFO ("setting interface of model `%s' to `%s'\n", current_model->name, string_entry); current_model->interface = string_entry; continue; } if (read_keyword (line, ":scsi", param_three_strings, &three_string_entry) == SANE_STATUS_GOOD) { if (!current_model) { DBG_WARN ("ignored `%s' :scsi, only allowed for " "hardware devices\n", current_backend->name); continue; } DBG_INFO ("setting scsi vendor and product ids of model `%s' to `%s/%s'\n", current_model->name, three_string_entry[0], three_string_entry[1]); if (strcasecmp (three_string_entry[0], "ignore") == 0) { DBG_INFO ("Ignoring `%s's scsi-entries of `%s'\n", current_backend->name, current_model->name); continue; } if (strcasecmp (three_string_entry[2], "processor") == 0){ current_model->scsi_is_processor = SANE_TRUE; current_model->scsi_vendor_id = three_string_entry[0]; current_model->scsi_product_id = three_string_entry[1]; } else { DBG_INFO ("scsi-format info in %s is invalid -> break\n", current_backend->name); continue; } continue; } if (read_keyword (line, ":usbid", param_two_strings, &two_string_entry) == SANE_STATUS_GOOD) { if (!current_model) { DBG_WARN ("ignored `%s' :usbid, only allowed for " "hardware devices\n", current_backend->name); continue; } if (strcasecmp (two_string_entry[0], "ignore") == 0) { DBG_INFO ("Ignoring `%s's USB ids of `%s'\n", current_backend->name, current_model->name); current_model->ignore_usb_id = SANE_TRUE; continue; } if (!check_hex (two_string_entry[0])) { DBG_WARN ("`%s's USB vendor id of `%s' is " "not a lowercase 4-digit hex number: " "`%s'\n", current_backend->name, current_model->name, two_string_entry[0]); continue; } if (!check_hex (two_string_entry[1])) { DBG_WARN ("`%s's USB product id of `%s' is " "not a lowercase 4-digit hex number: " "`%s'\n", current_backend->name, current_model->name, two_string_entry[1]); continue; } if (current_model->usb_vendor_id || current_model->usb_product_id) { DBG_WARN ("overwriting `%s's USB ids of model " "`%s' to `%s/%s' (was: `%s/%s')\n", current_backend->name, current_model->name, two_string_entry[0], two_string_entry[1], current_model->usb_vendor_id, current_model->usb_product_id); } DBG_INFO ("setting USB vendor and product ids of model `%s' to `%s/%s'\n", current_model->name, two_string_entry[0], two_string_entry[1]); current_model->usb_vendor_id = two_string_entry[0]; current_model->usb_product_id = two_string_entry[1]; continue; } if (read_keyword (line, ":url", param_string, &string_entry) == SANE_STATUS_GOOD) { switch (current_level) { case level_backend: current_backend->url = update_url_list (current_backend->url, string_entry); DBG_INFO ("adding `%s' to list of urls of backend " "`%s'\n", string_entry, current_backend->name); break; case level_mfg: current_mfg->url = update_url_list (current_mfg->url, string_entry); DBG_INFO ("adding `%s' to list of urls of mfg " "`%s'\n", string_entry, current_mfg->name); break; case level_desc: current_type->desc->url = update_url_list (current_type->desc->url, string_entry); DBG_INFO ("adding `%s' to list of urls of description " "for backend `%s'\n", string_entry, current_backend->name); break; case level_model: current_model->url = update_url_list (current_model->url, string_entry); DBG_INFO ("adding `%s' to list of urls of model " "`%s'\n", string_entry, current_model->name); break; default: DBG_ERR ("level %d not implemented for :url (backend `%s')\n", current_level, current_backend->name); return SANE_FALSE; } continue; } if (read_keyword (line, ":comment", param_string, &string_entry) == SANE_STATUS_GOOD) { switch (current_level) { case level_backend: current_backend->comment = string_entry; DBG_INFO ("setting comment of backend %s to `%s'\n", current_backend->name, string_entry); break; case level_mfg: current_mfg->comment = string_entry; DBG_INFO ("setting comment of manufacturer %s to `%s'\n", current_mfg->name, string_entry); break; case level_desc: current_type->desc->comment = string_entry; DBG_INFO ("setting comment of description for " "backend %s to `%s'\n", current_backend->name, string_entry); break; case level_model: current_model->comment = string_entry; DBG_INFO ("setting comment of model %s to `%s'\n", current_model->name, string_entry); break; default: DBG_ERR ("level %d not implemented for `:comment' (backend `%s')\n", current_level, current_backend->name); return SANE_FALSE; } continue; } DBG_ERR ("unknown keyword token in line `%s' of file `%s'\n", line, file_name); return SANE_FALSE; } /* while (sanei_config_readline) */ fclose (fp); } /* if (strlen) */ } /* while (direntry) */ if (closedir(dir) != 0) { DBG_ERR ("cannot close directory `%s' (%s)\n", search_dir, strerror (errno)); return SANE_FALSE; } if (end) search_dir = end + 1; else search_dir = (search_dir + strlen (search_dir)); } desc_name = 0; if (!first_backend) { DBG_ERR ("Couldn't find any .desc file\n"); return SANE_FALSE; } return SANE_TRUE; } /* Create a model_record_entry based on a model_entry */ static model_record_entry * create_model_record (model_entry * model) { model_record_entry *model_record; model_record = calloc (1, sizeof (model_record_entry)); if (!model_record) { DBG_ERR ("create_model_record: couldn't calloc model_record_entry\n"); exit (1); } model_record->name = model->name; model_record->status = model->status; model_record->interface = model->interface; model_record->url = model->url; model_record->comment = model->comment; model_record->usb_vendor_id = model->usb_vendor_id; model_record->usb_product_id = model->usb_product_id; model_record->scsi_vendor_id = model->scsi_vendor_id; model_record->scsi_product_id = model->scsi_product_id; model_record->scsi_is_processor = model->scsi_is_processor; return model_record; } /* Calculate the priority of statuses: */ /* minimal, basic, good, complete -> 2, untested -> 1, unsupported -> 0 */ static int calc_priority (status_entry status) { switch (status) { case status_untested: return 1; case status_unsupported: return 0; default: return 2; } } /* Insert model into list at the alphabetically correct position */ static model_record_entry * update_model_record_list (model_record_entry * first_model_record, model_entry * model, backend_entry * be) { model_record_entry *model_record = first_model_record; if (!first_model_record) { /* First model for this manufacturer */ first_model_record = create_model_record (model); model_record = first_model_record; } else { model_record_entry *prev_model_record = 0; while (model_record) { int compare = string_compare (model->name, model_record->name); if (compare <= 0) { model_record_entry *tmp_model_record = model_record; if ((compare == 0) && (string_compare (model->interface, model_record->interface) == 0) && (string_compare (model->usb_vendor_id, model_record->usb_vendor_id) == 0) && (string_compare (model->usb_product_id, model_record->usb_product_id) == 0)) { /* Two entries for the same model */ int new_priority = calc_priority (model->status); int old_priority = calc_priority (model_record->status); if (new_priority < old_priority) { DBG_DBG ("update_model_record_list: model %s ignored, backend %s has " "higher priority\n", model->name, model_record->be->name); return first_model_record; } if (new_priority > old_priority) { DBG_DBG ("update_model_record_list: model %s overrides the one from backend %s\n", model->name, model_record->be->name); tmp_model_record = model_record->next; } } /* correct position */ model_record = create_model_record (model); model_record->next = tmp_model_record; if (!prev_model_record) first_model_record = model_record; else prev_model_record->next = model_record; break; } prev_model_record = model_record; model_record = model_record->next; } if (!model_record) /* last entry */ { prev_model_record->next = create_model_record (model); model_record = prev_model_record->next; } } /* if (first_model_record) */ model_record->be = be; DBG_DBG ("update_model_record_list: added model %s\n", model->name); return first_model_record; } /* Insert manufacturer into list at the alphabetically correct position, */ /* create new record if neccessary */ static mfg_record_entry * update_mfg_record_list (mfg_record_entry * first_mfg_record, mfg_entry * mfg, backend_entry * be) { model_entry *model = mfg->model; mfg_record_entry *mfg_record = first_mfg_record; while (mfg_record) { if (string_compare (mfg_record->name, mfg->name) == 0) { /* Manufacturer already exists */ url_entry *mfg_url = mfg->url; /* Manufacturer comments and (additional) URLs? */ if (!mfg_record->comment) mfg_record->comment = mfg->comment; while (mfg_url && mfg_url->name) { mfg_record->url = update_url_list (mfg_record->url, mfg_url->name); mfg_url = mfg_url->next; } break; } mfg_record = mfg_record->next; } if (!mfg_record) { /* Manufacturer doesn't exist yet */ url_entry *url = mfg->url; mfg_record = calloc (1, sizeof (mfg_record_entry)); if (!mfg_record) { DBG_ERR ("update_mfg_record_list: couldn't calloc " "mfg_record_entry\n"); exit (1); } mfg_record->name = mfg->name; mfg_record->comment = mfg->comment; while (url) { mfg_record->url = update_url_list (mfg_record->url, url->name); url = url->next; } if (first_mfg_record != 0) { /* We already have one manufacturer in the list */ mfg_record_entry *new_mfg_record = mfg_record; mfg_record_entry *prev_mfg_record = 0; mfg_record = first_mfg_record; while (mfg_record) { int compare = string_compare (new_mfg_record->name, mfg_record->name); if (compare <= 0) { mfg_record_entry *tmp_mfg_record = mfg_record; mfg_record = new_mfg_record; mfg_record->next = tmp_mfg_record; if (!prev_mfg_record) first_mfg_record = mfg_record; else prev_mfg_record->next = mfg_record; break; } prev_mfg_record = mfg_record; mfg_record = mfg_record->next; } if (!mfg_record) /* last entry */ { prev_mfg_record->next = new_mfg_record; mfg_record = prev_mfg_record->next; } } else first_mfg_record = mfg_record; DBG_DBG ("update_mfg_record_list: created mfg %s\n", mfg_record->name); } /* if (!mfg_record) */ /* create model entries */ while (model) { mfg_record->model_record = update_model_record_list (mfg_record->model_record, model, be); model = model->next; } return first_mfg_record; } /* Create a sorted list of manufacturers based on the backends list */ static mfg_record_entry * create_mfg_list (device_type dev_type) { mfg_record_entry *first_mfg_record = 0; backend_entry *be = first_backend; DBG_DBG ("create_mfg_list: start\n"); while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) { mfg_entry *mfg = type->mfg; while (mfg) { first_mfg_record = update_mfg_record_list (first_mfg_record, mfg, be); mfg = mfg->next; } } type = type->next; } be = be->next; } DBG_DBG ("create_mfg_list: exit\n"); return first_mfg_record; } /* Print an ASCII list with all the information we have */ static void ascii_print_backends (void) { backend_entry *be; be = first_backend; while (be) { url_entry *url = be->url; type_entry *type = be->type; if (be->name) printf ("backend `%s'\n", be->name); else printf ("backend *none*\n"); if (be->version) printf (" version `%s'\n", be->version); else printf (" version *none*\n"); if (be->new) printf (" NEW!\n"); if (be->manpage) printf (" manpage `%s'\n", be->manpage); else printf (" manpage *none*\n"); if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (be->comment) printf (" comment `%s'\n", be->comment); else printf (" comment *none*\n"); if (type) while (type) { switch (type->type) { case type_scanner: printf (" type scanner\n"); break; case type_stillcam: printf (" type stillcam\n"); break; case type_vidcam: printf (" type vidcam\n"); break; case type_meta: printf (" type meta\n"); break; case type_api: printf (" type api\n"); break; default: printf (" type *unknown*\n"); break; } if (type->desc) { url_entry *url = type->desc->url; printf (" desc `%s'\n", type->desc->desc); if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (type->desc->comment) printf (" comment `%s'\n", type->desc->comment); else printf (" comment *none*\n"); } else if (type->type >= type_meta) printf (" desc *none*\n"); if (type->mfg) { mfg_entry *mfg = type->mfg; while (mfg) { model_entry *model = mfg->model; url_entry *url = mfg->url; printf (" mfg `%s'\n", mfg->name); if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (mfg->comment) printf (" comment `%s'\n", mfg->comment); else printf (" comment *none*\n"); if (model) while (model) { url_entry *url = model->url; printf (" model `%s'\n", model->name); if (model->interface) printf (" interface `%s'\n", model->interface); else printf (" interface *none*\n"); if (model->usb_vendor_id) printf (" usb-vendor-id `%s'\n", model->usb_vendor_id); else printf (" usb-vendor-id *none*\n"); if (model->usb_product_id) printf (" usb-product-id `%s'\n", model->usb_product_id); else printf (" usb-product-id *none*\n"); switch (model->status) { case status_minimal: printf (" status minimal\n"); break; case status_basic: printf (" status basic\n"); break; case status_good: printf (" status good\n"); break; case status_complete: printf (" status complete\n"); break; case status_untested: printf (" status untested\n"); break; case status_unsupported: printf (" status unsupported\n"); break; default: printf (" status *unknown*\n"); break; } if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (model->comment) printf (" comment `%s'\n", model->comment); else printf (" comment *none*\n"); model = model->next; } else printf (" model *none*\n"); mfg = mfg->next; } /* while (mfg) */ } else if (type->type < type_meta) printf (" mfg *none*\n"); type = type->next; } /* while (type) */ else printf (" type *none*\n"); be = be->next; } /* while (be) */ } static char * clean_string (char *c) { /* not avoided characters */ char *aux; aux = malloc (strlen (c) * sizeof (char) * 6); *aux = '\0'; while (*c != '\0') { /*limit to printable ASCII only*/ if(*c < 0x20 || *c > 0x7e){ c++; continue; } switch (*c) { case '<': aux = strcat (aux, "<"); break; case '>': aux = strcat (aux, ">"); break; case '\'': aux = strcat (aux, "'"); break; case '&': aux = strcat (aux, "&"); break; default: aux = strncat (aux, c, 1); } c = c + 1; } return aux; } /* Print an XML list with all the information we have */ static void xml_print_backends (void) { backend_entry *be; be = first_backend; printf ("\n"); while (be) { url_entry *url = be->url; type_entry *type = be->type; if (be->name) printf ("\n", clean_string (be->name)); else printf ("\n"); if (be->version) printf ("%s\n", clean_string (be->version)); else printf ("*none*\n"); if (be->new) printf ("\n"); else printf ("\n"); if (be->manpage) printf (" %s\n", clean_string (be->manpage)); else printf (" *none*\n"); if (url) while (url) { printf (" %s\n", clean_string (url->name)); url = url->next; } else printf (" *none*\n"); if (be->comment) printf (" %s\n", clean_string (be->comment)); else printf (" *none*\n"); if (type) while (type) { switch (type->type) { case type_scanner: printf (" \n"); break; case type_stillcam: printf (" \n"); break; case type_vidcam: printf (" \n"); break; case type_meta: printf (" \n"); break; case type_api: printf (" \n"); break; default: printf (" \n"); break; } if (type->desc) { url_entry *url = type->desc->url; printf (" %s\n", clean_string (type->desc->desc)); if (url) while (url) { printf (" %s\n", clean_string (url->name)); url = url->next; } else printf (" *none*\n"); if (type->desc->comment) printf (" %s\n", clean_string (type->desc->comment)); else printf (" *none*\n"); } else if (type->type >= type_meta) printf (" *none*\n"); if (type->mfg) { mfg_entry *mfg = type->mfg; while (mfg) { model_entry *model = mfg->model; url_entry *url = mfg->url; printf (" \n", clean_string (mfg->name)); if (url) while (url) { printf (" `%s'\n", clean_string (url->name)); url = url->next; } else printf (" *none*\n"); if (mfg->comment) printf (" %s\n", clean_string (mfg->comment)); else printf (" *none*\n"); if (model) while (model) { url_entry *url = model->url; printf (" \n", clean_string (model->name)); if (model->interface) printf (" %s\n", clean_string (model->interface)); else printf (" *none*\n"); if (model->usb_vendor_id) printf (" %s\n", clean_string (model->usb_vendor_id)); else printf (" *none*\n"); if (model->usb_product_id) printf (" %s\n", clean_string (model->usb_product_id)); else printf (" *none*\n"); switch (model->status) { case status_minimal: printf (" minimal\n"); break; case status_basic: printf (" basic\n"); break; case status_good: printf (" good\n"); break; case status_complete: printf (" complete\n"); break; case status_untested: printf (" untested\n"); break; case status_unsupported: printf (" unsupported\n"); break; default: printf (" *unknown*\n"); break; } if (url) while (url) { printf (" %s\n", clean_string (url->name)); url = url->next; } else printf (" *none*\n"); if (model->comment) printf (" %s\n", clean_string (model->comment)); else printf (" *none*\n"); model = model->next; printf (" \n"); } /* while (model) */ else printf (" \n"); printf (" \n"); mfg = mfg->next; } /* while (mfg) */ } else if (type->type < type_meta) printf (" *none*\n"); type = type->next; printf (" \n"); } /* while (type) */ else printf (" *none*\n"); printf ("\n"); be = be->next; } /* while (be) */ printf ("\n"); } /* calculate statistics about supported devices per device type*/ static void calculate_statistics_per_type (device_type dev_type, statistics_type num) { backend_entry *be = first_backend; while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) { mfg_entry *mfg = type->mfg; model_entry *model; if (type->desc) { num[status_complete]++; type = type->next; continue; } if (!mfg) { type = type->next; continue; } mfg = type->mfg; while (mfg) { model = mfg->model; if (model) { while (model) { enum status_entry status = model->status; num[status]++; model = model->next; } /* while (model) */ } /* if (num_models) */ mfg = mfg->next; } /* while (mfg) */ } /* if (type->type) */ type = type->next; } /* while (type) */ be = be->next; } /* while (be) */ } static void html_print_statistics_cell (const char * color, int number) { printf ("%d\n", color, number); } static void html_print_statistics_per_type (device_type dev_type) { statistics_type num = {0, 0, 0, 0, 0, 0, 0}; status_entry status; calculate_statistics_per_type (dev_type, num); printf ("\n"); printf("%s\n", device_type_aname [dev_type], device_type_name [dev_type]); html_print_statistics_cell (COLOR_UNKNOWN, num[status_minimal] + num[status_basic] + num[status_good] + num[status_complete] + num[status_untested] + num[status_unsupported]); if (dev_type == type_scanner || dev_type == type_stillcam || dev_type == type_vidcam) { html_print_statistics_cell (COLOR_UNKNOWN, num[status_minimal] + num[status_basic] + num[status_good] + num[status_complete]); for (status = status_complete; status >= status_unsupported; status--) html_print_statistics_cell (status_color [status], num [status]); } else { printf ("n/a\n"); } printf ("\n"); } /* print html statistcis */ static void html_print_summary (void) { device_type dev_type; status_entry status; printf ("

Summary

\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n", status_name[status_untested]); printf ("\n", status_name[status_unsupported]); printf ("\n"); printf ("\n"); printf ("\n"); for (status = status_complete; status >= status_minimal; status--) printf ("\n", status_color[status], status_name[status]); printf ("\n"); for (dev_type = type_scanner; dev_type <= type_api; dev_type++) html_print_statistics_per_type (dev_type); printf ("
Device typeNumber of devices
TotalSupported%s%s
Sum%s
\n"); } /* Generate a name used for HTML tags */ static char * html_generate_anchor_name (device_type dev_type, char *manufacturer_name) { char *name = malloc (strlen (manufacturer_name) + 1 + 2); char *pointer = name; char type_char; if (!name) { DBG_ERR ("html_generate_anchor_name: couldn't malloc\n"); return 0; } switch (dev_type) { case type_scanner: type_char = 'S'; break; case type_stillcam: type_char = 'C'; break; case type_vidcam: type_char = 'V'; break; case type_meta: type_char = 'M'; break; case type_api: type_char = 'A'; break; default: type_char = 'Z'; break; } snprintf (name, strlen (manufacturer_name) + 1 + 2, "%c-%s", type_char, manufacturer_name); while (*pointer) { if (!isalnum (*pointer)) *pointer = '-'; else *pointer = toupper (*pointer); pointer++; } return name; } /* Generate one table per backend of all backends providing models */ /* of type dev_type */ static void html_backends_split_table (device_type dev_type) { backend_entry *be = first_backend; SANE_Bool first = SANE_TRUE; printf ("

Backends:\n"); while (be) /* print link list */ { type_entry *type = be->type; SANE_Bool found = SANE_FALSE; while (type) { if (type->type == dev_type) found = SANE_TRUE; type = type->next; } if (found) { if (!first) printf (",\n"); first = SANE_FALSE; printf ("%s", html_generate_anchor_name (dev_type, be->name), be->name); } be = be->next; } be = first_backend; if (first) printf ("(none)\n"); printf ("

\n"); while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) { mfg_entry *mfg = type->mfg; model_entry *model; printf ("

Backend: %s\n", html_generate_anchor_name (type->type, be->name), be->name); if (be->version || be->new) { printf ("("); if (be->version) { printf ("%s", be->version); if (be->new) printf (", NEW!"); } else printf ("NEW!"); printf (")\n"); } printf ("

\n"); printf ("

\n"); if (be->url && be->url->name) { url_entry *url = be->url; printf ("Link(s):\n"); while (url) { if (url != be->url) printf (", "); printf ("%s", url->name, url->name); url = url->next; } printf ("
\n"); } if (be->manpage) printf ("Manual page: %s
\n", be->manpage, be->manpage); if (be->comment) printf ("Comment: %s
\n", be->comment); if (type->desc) { if (type->desc->desc) { if (type->desc->url && type->desc->url->name) printf ("Description: " "%s
\n", type->desc->url->name, type->desc->desc); else printf ("Description: %s
\n", type->desc->desc); } if (type->desc->comment) printf ("Comment: %s
\n", type->desc->comment); printf ("

\n"); type = type->next; continue; } printf ("

\n"); if (!mfg) { type = type->next; continue; } printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); mfg = type->mfg; while (mfg) { model = mfg->model; if (model) { int num_models = 0; while (model) /* count models for rowspan */ { model = model->next; num_models++; } model = mfg->model; printf ("\n"); printf ("\n"); if (model->url && model->url->name) printf ("\n", model->url->name, model->name); else printf ("\n", model->name); if (model->interface) printf ("\n", model->interface); else printf ("\n"); if (model->usb_vendor_id && model->usb_product_id) printf ("\n", model->usb_vendor_id, model->usb_product_id); else printf ("\n"); printf ("\n", status_color[status], status_name[status]); if (model->comment && model->comment[0] != 0) printf ("\n", model->comment); else printf ("\n"); model = model->next; printf ("\n"); } /* while (model) */ } /* if (num_models) */ mfg = mfg->next; } /* while (mfg) */ printf ("
ManufacturerModelInterfaceUSB idStatusComment
\n", num_models); if (mfg->url && mfg->url->name) printf ("%s\n", mfg->url->name, mfg->name); else printf ("%s\n", mfg->name); while (model) { enum status_entry status = model->status; if (model != mfg->model) printf ("
%s%s%s?%s/%s %s%s 
\n"); } /* if (type->type) */ type = type->next; } /* while (type) */ be = be->next; } /* while (be) */ /* printf ("\n"); */ } /* Generate one table per manufacturer constructed of all backends */ /* providing models of type dev_type */ static void html_mfgs_table (device_type dev_type) { mfg_record_entry *mfg_record = 0, *first_mfg_record = 0; first_mfg_record = create_mfg_list (dev_type); mfg_record = first_mfg_record; printf ("

Manufacturers:\n"); while (mfg_record) { if (mfg_record != first_mfg_record) printf (",\n"); printf ("%s", html_generate_anchor_name (type_unknown, mfg_record->name), mfg_record->name); mfg_record = mfg_record->next; } mfg_record = first_mfg_record; if (!mfg_record) printf ("(none)\n"); printf ("

\n"); while (mfg_record) { model_record_entry *model_record = mfg_record->model_record; printf ("

Manufacturer: %s

\n", html_generate_anchor_name (type_unknown, mfg_record->name), mfg_record->name); printf ("

\n"); if (mfg_record->url && mfg_record->url->name) { url_entry *url = mfg_record->url; printf ("Link(s):\n"); while (url) { if (url != mfg_record->url) printf (", "); printf ("%s", url->name, url->name); url = url->next; } printf ("
\n"); } if (mfg_record->comment) printf ("Comment: %s
\n", mfg_record->comment); printf ("

\n"); if (!model_record) { mfg_record = mfg_record->next; continue; } printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); while (model_record) { enum status_entry status = model_record->status; if (model_record->url && model_record->url->name) printf ("\n", model_record->url->name, model_record->name); else printf ("\n", model_record->name); if (model_record->interface) printf ("\n", model_record->interface); else printf ("\n"); if (model_record->usb_vendor_id && model_record->usb_product_id) printf ("\n", model_record->usb_vendor_id, model_record->usb_product_id); else printf ("\n"); printf ("\n", status_color[status], status_name[status]); if (model_record->comment && model_record->comment[0] != 0) printf ("\n", model_record->comment); else printf ("\n"); printf ("\n"); if (model_record->be->manpage) printf ("\n", model_record->be->manpage, model_record->be->manpage); else printf ("\n"); printf ("\n"); model_record = model_record->next; } /* while model_record */ printf ("
ModelInterfaceUSB idStatusCommentBackendManpage
%s
%s%s?%s/%s %s%s \n"); if (model_record->be->url && model_record->be->url->name) printf ("%s\n", model_record->be->url->name, model_record->be->name); else printf ("%s", model_record->be->name); if (model_record->be->version || model_record->be->new) { printf ("
("); if (model_record->be->version) { printf ("%s", model_record->be->version); if (model_record->be->new) printf (", NEW!"); } else printf ("NEW!"); printf (")\n"); } printf ("
%s?
\n"); mfg_record = mfg_record->next; } /* while mfg_record */ } /* Print the HTML headers and an introduction */ static void html_print_header (void) { printf ("\n" " \n" "\n"); printf ("%s\n", title); printf ("\n" "\n" "
\n" "\"SANE\"\n"); printf ("

%s

\n", title); printf ("
\n" "
\n"); printf ("%s\n", intro); printf ("

This is only a summary!\n" "Please consult the manpages and the author-supplied webpages\n" "for more detailed (and usually important) information\n" "concerning each backend.

\n"); printf ("

If you have new information or corrections, please file a\n" "bug report\n" "with as many details as possible. Also please tell us if your scanner\n" "isn't mentioned in this list at all.

\n" "

For an explanation of the tables, see the\n" "legend.\n"); } /* Print the HTML footers and contact information */ static void html_print_footer (void) { printf ("


\n" "SANE homepage\n" "
\n" "Contact\n" "
\n" "\n"); printf ("This page was created by sane-desc %s from %s\n", SANE_DESC_VERSION, PACKAGE_STRING); printf ("\n"); printf (" \n"); } /* print parts of the legend */ static void html_print_legend_backend (void) { printf ("
Backend:
\n" "
Name of the backend, in parentheses if available:\n" " Version of backend/driver; newer versions may be\n" " available from their home sites.
" " NEW! means brand-new to the\n" " current release of SANE.
\n" " UNMAINTAINED means that nobody maintains that backend. Expect no\n" " new features or newly supported devices. You are welcome to take over\n" " maintainership.\n" "
\n"); } static void html_print_legend_link (void) { printf ("
Link(s):
\n" "
Link(s) to more extensive and\n" " detailed information, if it exists, or the email address\n" " of the author or maintainer.\n"); } static void html_print_legend_manual (void) { printf ("
Manual Page:
\n" "
A link to the man-page online, if it exists.
\n"); } static void html_print_legend_comment (void) { printf ("
Comment:
\n" "
More information about the backend or model, e.g. the level of " " support and possible problems.
\n"); } static void html_print_legend_manufacturer (void) { printf ("
Manufacturer:
\n" "
Manufacturer, vendor or brand name of the device.
\n"); } static void html_print_legend_model (void) { printf ("
Model:
\n" "
Name of the device.
\n"); } static void html_print_legend_interface (void) { printf ("
Interface:
\n" "
How the device is connected to the computer.
\n"); } static void html_print_legend_usbid (void) { printf ("
USB id:
\n" "
The USB vendor and product ids as printed by sane-find-scanner -q (only applicable for USB devices).
\n"); } static void html_print_legend_status (void) { printf ("
Status:
\n" "
Indicates how many of the features the device provides\n" " are supported by SANE.\n" "
  • unsupported" " means the device is not supported at least by this backend. " " It may be supported by other backends, however.\n"); printf ("
  • untested means the " " device may be supported but couldn't be tested. Be very " " careful and report success/failure.\n" "
  • minimal means that the\n" " device is detected and scans at least in one mode. But the quality\n" " is bad or important features won't work.\n"); printf ("
  • basic means it works at\n" " least in the most important modes but quality is not perfect.\n" "
  • good means the device is usable\n" " for day-to-day work. Some rather exotic features may be missing.\n" "
  • complete means the backends\n" " supports everything the device can do.\n" "
\n"); } static void html_print_legend_description (void) { printf ("
Description:
\n" "
The scope of application of the backend.\n"); } /* Print the HTML page with one table of models per backend */ static void html_print_backends_split (void) { if (!title) title = "SANE: Backends (Drivers)"; if (!intro) intro = "

The following table summarizes the backends/drivers " "distributed with the latest version of sane-backends, and the hardware " "or software they support.

"; html_print_header (); html_print_summary (); printf ("

Scanners

\n"); html_backends_split_table (type_scanner); printf ("

Still Cameras

\n"); html_backends_split_table (type_stillcam); printf ("

Video Cameras

\n"); html_backends_split_table (type_vidcam); printf ("

APIs

\n"); html_backends_split_table (type_api); printf ("

Meta Backends

\n"); html_backends_split_table (type_meta); printf ("

Legend:

\n" "
\n"); html_print_legend_backend (); html_print_legend_link (); html_print_legend_manual (); html_print_legend_comment (); html_print_legend_manufacturer (); html_print_legend_model (); html_print_legend_interface (); html_print_legend_usbid (); html_print_legend_status (); html_print_legend_description (); printf ("
\n"); html_print_footer (); } /* Print the HTML page with one table of models per manufacturer */ static void html_print_mfgs (void) { if (!title) title = "SANE: Supported Devices"; if (!intro) intro = "

The following table summarizes the devices supported " "by the latest version of sane-backends.

"; html_print_header (); html_print_summary (); printf ("

Scanners

\n"); html_mfgs_table (type_scanner); printf ("

Still Cameras

\n"); html_mfgs_table (type_stillcam); printf ("

Video Cameras

\n"); html_mfgs_table (type_vidcam); printf ("

APIs

\n"); html_backends_split_table (type_api); printf ("

Meta Backends

\n"); html_backends_split_table (type_meta); printf ("

Legend:

\n" "
\n" "
\n"); html_print_legend_model (); html_print_legend_interface (); html_print_legend_usbid (); html_print_legend_status (); html_print_legend_comment (); html_print_legend_backend (); html_print_legend_manual (); html_print_legend_manufacturer (); html_print_legend_description (); printf ("
\n" "
\n"); html_print_footer (); } /* print statistics about supported devices */ static void print_statistics_per_type (device_type dev_type) { statistics_type num = {0, 0, 0, 0, 0, 0, 0}; calculate_statistics_per_type (dev_type, num); printf (" Total: %4d\n", num[status_minimal] + num[status_basic] + num[status_good] + num[status_complete] + num[status_untested] + num[status_untested] + num[status_unsupported]); if (dev_type == type_scanner || dev_type == type_stillcam || dev_type == type_vidcam) { printf (" Supported: %4d (complete: %d, good: %d, basic: %d, " "minimal: %d)\n", num[status_minimal] + num[status_basic] + num[status_good] + num[status_complete], num[status_complete], num[status_good], num[status_basic], num[status_minimal]); printf (" Untested: %4d\n", num[status_untested]); printf (" Unsupported: %4d\n", num[status_unsupported]); } } static void print_statistics (void) { printf ("Number of known devices:\n"); printf ("Scanners:\n"); print_statistics_per_type (type_scanner); printf ("Still cameras:\n"); print_statistics_per_type (type_stillcam); printf ("Video cameras:\n"); print_statistics_per_type (type_vidcam); printf ("Meta backends:\n"); print_statistics_per_type (type_meta); printf ("API backends:\n"); print_statistics_per_type (type_api); } static usbid_type * create_usbid (char *manufacturer, char *model, char *usb_vendor_id, char *usb_product_id) { usbid_type * usbid = calloc (1, sizeof (usbid_type)); usbid->usb_vendor_id = strdup (usb_vendor_id); usbid->usb_product_id = strdup (usb_product_id); usbid->name = calloc (1, sizeof (manufacturer_model_type)); usbid->name->name = calloc (1, strlen (manufacturer) + strlen (model) + 3); sprintf (usbid->name->name, "%s %s", manufacturer, model); usbid->name->next = 0; usbid->next = 0; DBG_DBG ("New USB ids: %s/%s (%s %s)\n", usb_vendor_id, usb_product_id, manufacturer, model); return usbid; } static scsiid_type * create_scsiid (char *manufacturer, char *model, char *scsi_vendor_id, char *scsi_product_id, SANE_Bool is_processor) { scsiid_type * scsiid = calloc (1, sizeof (scsiid_type)); scsiid->scsi_vendor_id = strdup (scsi_vendor_id); scsiid->scsi_product_id = strdup (scsi_product_id); scsiid->is_processor = is_processor; scsiid->name = calloc (1, sizeof (manufacturer_model_type)); scsiid->name->name = calloc (1, strlen (manufacturer) + strlen (model) + 3); sprintf (scsiid->name->name, "%s %s", manufacturer, model); scsiid->name->next = 0; scsiid->next = 0; DBG_DBG ("New SCSI ids: %s/%s (%s %s)\n", scsi_vendor_id, scsi_product_id, manufacturer, model); return scsiid; } static usbid_type * add_usbid (usbid_type *first_usbid, char *manufacturer, char *model, char *usb_vendor_id, char *usb_product_id) { usbid_type *usbid = first_usbid; usbid_type *prev_usbid = 0, *tmp_usbid = 0; if (!first_usbid) first_usbid = create_usbid (manufacturer, model, usb_vendor_id, usb_product_id); else { while (usbid) { if (strcmp (usb_vendor_id, usbid->usb_vendor_id) == 0 && strcmp (usb_product_id, usbid->usb_product_id) == 0) { manufacturer_model_type *man_mod = usbid->name; while (man_mod->next) man_mod = man_mod->next; man_mod->next = malloc (sizeof (manufacturer_model_type)); man_mod->next->name = malloc (strlen (manufacturer) + strlen (model) + 3); sprintf (man_mod->next->name, "%s %s", manufacturer, model); man_mod->next->next = 0; DBG_DBG ("Added manufacturer/model %s %s to USB ids %s/%s\n", manufacturer, model, usb_vendor_id, usb_product_id); break; } if (strcmp (usb_vendor_id, usbid->usb_vendor_id) < 0 || (strcmp (usb_vendor_id, usbid->usb_vendor_id) == 0 && strcmp (usb_product_id, usbid->usb_product_id) < 0)) { tmp_usbid = create_usbid (manufacturer, model, usb_vendor_id, usb_product_id); tmp_usbid->next = usbid; if (prev_usbid) prev_usbid->next = tmp_usbid; else first_usbid = tmp_usbid; break; } prev_usbid = usbid; usbid = usbid->next; } if (!usbid) { prev_usbid->next = create_usbid (manufacturer, model, usb_vendor_id, usb_product_id); usbid = prev_usbid->next; } } return first_usbid; } static scsiid_type * add_scsiid (scsiid_type *first_scsiid, char *manufacturer, char *model, char *scsi_vendor_id, char *scsi_product_id, SANE_Bool is_processor) { scsiid_type *scsiid = first_scsiid; scsiid_type *prev_scsiid = 0, *tmp_scsiid = 0; if (!first_scsiid) first_scsiid = create_scsiid (manufacturer, model, scsi_vendor_id, scsi_product_id, is_processor); else { while (scsiid) { if (strcmp (scsi_vendor_id, scsiid->scsi_vendor_id) == 0 && strcmp (scsi_product_id, scsiid->scsi_product_id) == 0) { manufacturer_model_type *man_mod = scsiid->name; while (man_mod->next) man_mod = man_mod->next; man_mod->next = malloc (sizeof (manufacturer_model_type)); man_mod->next->name = malloc (strlen (manufacturer) + strlen (model) + 3); sprintf (man_mod->next->name, "%s %s", manufacturer, model); man_mod->next->next = 0; DBG_DBG ("Added manufacturer/model %s %s to SCSI ids %s/%s\n", manufacturer, model, scsi_vendor_id, scsi_product_id); break; } if (strcmp (scsi_vendor_id, scsiid->scsi_vendor_id) < 0 || (strcmp (scsi_vendor_id, scsiid->scsi_vendor_id) == 0 && strcmp (scsi_product_id, scsiid->scsi_product_id) < 0)) { tmp_scsiid = create_scsiid (manufacturer, model, scsi_vendor_id, scsi_product_id, is_processor); tmp_scsiid->next = scsiid; if (prev_scsiid) prev_scsiid->next = tmp_scsiid; else first_scsiid = tmp_scsiid; break; } prev_scsiid = scsiid; scsiid = scsiid->next; } if (!scsiid) { prev_scsiid->next = create_scsiid (manufacturer, model, scsi_vendor_id, scsi_product_id, is_processor); scsiid = prev_scsiid->next; } } return first_scsiid; } static usbid_type * create_usbids_table (void) { backend_entry *be; usbid_type *first_usbid = NULL; if (!first_backend) return NULL; for (be = first_backend; be; be = be->next) { type_entry *type; if (!be->type) continue; for (type = be->type; type; type = type->next) { mfg_entry *mfg; if (!type->mfg) continue; for (mfg = type->mfg; mfg; mfg = mfg->next) { model_entry *model; if (!mfg->model) continue; for (model = mfg->model; model; model = model->next) { if ((model->status == status_unsupported) || (model->status == status_unknown)) continue; if (model->usb_vendor_id && model->usb_product_id) { first_usbid = add_usbid (first_usbid, mfg->name, model->name, model->usb_vendor_id, model->usb_product_id); } } /* for (model) */ } /* for (mfg) */ } /* for (type) */ } /* for (be) */ return first_usbid; } static scsiid_type * create_scsiids_table (void) { backend_entry *be; scsiid_type *first_scsiid = NULL; if (!first_backend) return NULL; for (be = first_backend; be; be = be->next) { type_entry *type; if (!be->type) continue; for (type = be->type; type; type = type->next) { mfg_entry *mfg; if (!type->mfg) continue; for (mfg = type->mfg; mfg; mfg = mfg->next) { model_entry *model; if (!mfg->model) continue; for (model = mfg->model; model; model = model->next) { if ((model->status == status_unsupported) || (model->status == status_unknown)) continue; if (model->scsi_vendor_id && model->scsi_product_id) { first_scsiid = add_scsiid (first_scsiid, mfg->name, model->name, model->scsi_vendor_id, model->scsi_product_id, model->scsi_is_processor); } } /* for (model) */ } /* for (mfg) */ } /* for (type) */ } /* for (be) */ return first_scsiid; } static void print_header_comment (void) { printf ("# This file was generated from description files (*.desc)\n" "# by sane-desc %s from %s\n", SANE_DESC_VERSION, PACKAGE_STRING); } /* print USB usermap file to be used by the hotplug tools */ static void print_usermap_header (void) { print_header_comment (); printf ("#\n" "# The entries below are used to detect a USB device and change owner\n" "# and permissions on the \"device node\" used by libusb.\n" "#\n" "# The 0x0003 match flag means the device is matched by its vendor and\n" "# product IDs.\n" "#\n" "# Sample entry (replace 0xVVVV and 0xPPPP with vendor ID and product ID\n" "# respectively):\n" "#\n" ); printf ("# libusbscanner 0x0003 0xVVVV 0xPPPP 0x0000 0x0000 0x00 0x00 0x00 0x00 " "0x00 0x00 0x00000000\n" "# usb module match_flags idVendor idProduct bcdDevice_lo bcdDevice_hi " "bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass " "bInterfaceSubClass bInterfaceProtocol driver_info\n" "#\n" ); printf ("# If your scanner isn't listed below, you can add it as explained above.\n" "#\n" "# If your scanner is supported by some external backend (brother, epkowa,\n" "# hpaio, etc) please ask the author of the backend to provide proper\n" "# device detection support for your OS\n" "#\n" "# If the scanner is supported by sane-backends, please mail the entry to\n" "# the sane-devel mailing list (sane-devel@alioth-lists.debian.net).\n" "#\n" ); } static void print_usermap (void) { usbid_type *usbid = create_usbids_table (); print_usermap_header (); while (usbid) { manufacturer_model_type * name = usbid->name; printf ("# "); while (name) { if (name != usbid->name) printf (" | "); printf ("%s", name->name); name = name->next; } printf ("\n"); printf ("libusbscanner 0x0003 %s %s ", usbid->usb_vendor_id, usbid->usb_product_id); printf ("0x0000 0x0000 0x00 0x00 0x00 0x00 0x00 0x00 0x00000000\n"); usbid = usbid->next; } } /* print libsane.db file for hotplug-ng */ static void print_db_header (void) { print_header_comment (); printf ("#\n" "# The entries below are used to detect a USB device when it's plugged in\n" "# and then run a script to change the ownership and\n" "# permissions on the \"device node\" used by libusb.\n" "# Sample entry (replace 0xVVVV and 0xPPPP with vendor ID and product ID\n" "# respectively):\n"); printf ("#\n" "# 0xVVVV0xPPPP%s:%s%s[/usr/local/bin/foo.sh]\n" "# Fields:\n" "# vendor ID\n" "# product ID\n" "# ownership (user:group)\n" "# permissions\n" "# path of an optional script to run (it can be omitted)\n" "#\n" , DEVOWNER, DEVGROUP, DEVMODE); printf ("# If your scanner isn't listed below, you can add it as explained above.\n" "#\n" "# If your scanner is supported by some external backend (brother, epkowa,\n" "# hpaio, etc) please ask the author of the backend to provide proper\n" "# device detection support for your OS\n" "#\n" "# If the scanner is supported by sane-backends, please mail the entry to\n" "# the sane-devel mailing list (sane-devel@alioth-lists.debian.net).\n" "#\n" ); } static void print_db (void) { usbid_type *usbid = create_usbids_table (); print_db_header (); while (usbid) { manufacturer_model_type * name = usbid->name; printf ("# "); while (name) { if (name != usbid->name) printf (" | "); printf ("%s", name->name); name = name->next; } printf ("\n"); printf ("%s\t%s\t%s:%s\t%s\n", usbid->usb_vendor_id, usbid->usb_product_id, DEVOWNER, DEVGROUP, DEVMODE); usbid = usbid->next; } } /* print libsane.rules for Linux udev */ static void print_udev_header (void) { print_header_comment (); printf ("#\n" "# udev rules file for supported USB and SCSI devices\n" "#\n" "# The SCSI device support is very basic and includes only\n" "# scanners that mark themselves as type \"scanner\" or\n" "# SCSI-scanners from HP and other vendors that are entitled \"processor\"\n" "# but are treated accordingly.\n" "#\n"); printf ("# To add a USB device, add a rule to the list below between the\n" "# LABEL=\"libsane_usb_rules_begin\" and LABEL=\"libsane_usb_rules_end\" lines.\n" "#\n" "# To run a script when your device is plugged in, add RUN+=\"/path/to/script\"\n" "# to the appropriate rule.\n" "#\n" ); printf ("# If your scanner isn't listed below, you can add it as explained above.\n" "#\n" "# If your scanner is supported by some external backend (brother, epkowa,\n" "# hpaio, etc) please ask the author of the backend to provide proper\n" "# device detection support for your OS\n" "#\n" "# If the scanner is supported by sane-backends, please mail the entry to\n" "# the sane-devel mailing list (sane-devel@alioth-lists.debian.net).\n" "#\n" ); } static void print_udev (void) { usbid_type *usbid = create_usbids_table (); scsiid_type *scsiid = create_scsiids_table (); int i; print_udev_header (); printf("ACTION!=\"add\", GOTO=\"libsane_rules_end\"\n" "ENV{DEVTYPE}==\"usb_device\", GOTO=\"libsane_create_usb_dev\"\n" "SUBSYSTEMS==\"scsi\", GOTO=\"libsane_scsi_rules_begin\"\n" "SUBSYSTEM==\"usb_device\", GOTO=\"libsane_usb_rules_begin\"\n" "SUBSYSTEM!=\"usb_device\", GOTO=\"libsane_usb_rules_end\"\n" "\n"); printf("# Kernel >= 2.6.22 jumps here\n" "LABEL=\"libsane_create_usb_dev\"\n" "\n"); printf("# For Linux >= 2.6.22 without CONFIG_USB_DEVICE_CLASS=y\n" "# If the following rule does not exist on your system yet, uncomment it\n" "# ENV{DEVTYPE}==\"usb_device\", " "MODE=\"0664\", OWNER=\"root\", GROUP=\"root\"\n" "\n"); printf("# Kernel < 2.6.22 jumps here\n" "LABEL=\"libsane_usb_rules_begin\"\n" "\n"); while (usbid) { manufacturer_model_type * name = usbid->name; i = 0; printf ("# "); while (name) { if ((name != usbid->name) && (i > 0)) printf (" | "); printf ("%s", name->name); name = name->next; i++; /* * Limit the number of model names on the same line to 3, * as udev cannot handle very long lines and prints a warning * message while loading the rules files. */ if ((i == 3) && (name != NULL)) { printf("\n# "); i = 0; } } printf ("\n"); if (mode == output_mode_udevacl) printf ("ATTRS{idVendor}==\"%s\", ATTRS{idProduct}==\"%s\", ENV{libsane_matched}=\"yes\"\n", usbid->usb_vendor_id + 2, usbid->usb_product_id + 2); else printf ("ATTRS{idVendor}==\"%s\", ATTRS{idProduct}==\"%s\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", usbid->usb_vendor_id + 2, usbid->usb_product_id + 2, DEVMODE, DEVGROUP); usbid = usbid->next; } printf("\n# The following rule will disable USB autosuspend for the device\n"); printf("ENV{libsane_matched}==\"yes\", RUN+=\"/bin/sh -c 'if test -e /sys/$env{DEVPATH}/power/control; then echo on > /sys/$env{DEVPATH}/power/control; elif test -e /sys/$env{DEVPATH}/power/level; then echo on > /sys/$env{DEVPATH}/power/level; fi'\"\n"); printf ("\nLABEL=\"libsane_usb_rules_end\"\n\n"); printf ("SUBSYSTEMS==\"scsi\", GOTO=\"libsane_scsi_rules_begin\"\n"); printf ("GOTO=\"libsane_scsi_rules_end\"\n\n"); printf ("LABEL=\"libsane_scsi_rules_begin\"\n"); printf ("# Generic: SCSI device type 6 indicates a scanner\n"); if (mode == output_mode_udevacl) printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"6\", ENV{libsane_matched}=\"yes\"\n"); else printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"6\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", DEVMODE, DEVGROUP); printf ("# Some scanners advertise themselves as SCSI device type 3\n"); printf ("# Wildcard: for some Epson SCSI scanners\n"); if (mode == output_mode_udevacl) printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"EPSON\", ATTRS{model}==\"SCANNER*\", ENV{libsane_matched}=\"yes\"\n"); else printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"EPSON\", ATTRS{model}==\"SCANNER*\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", DEVMODE, DEVGROUP); while (scsiid) { manufacturer_model_type * name = scsiid->name; if (!scsiid->is_processor) { scsiid = scsiid->next; continue; } /* Wildcard for Epson scanners: vendor = EPSON, product = SCANNER* */ if ((strcmp(scsiid->scsi_vendor_id, "EPSON") == 0) && (strncmp(scsiid->scsi_product_id, "SCANNER", 7) == 0)) { scsiid = scsiid->next; continue; } i = 0; printf ("# "); while (name) { if ((name != scsiid->name) && (i > 0)) printf (" | "); printf ("%s", name->name); name = name->next; i++; /* * Limit the number of model names on the same line to 3, * as udev cannot handle very long lines and prints a warning * message while loading the rules files. */ if ((i == 3) && (name != NULL)) { printf("\n# "); i = 0; } } printf ("\n"); if (mode == output_mode_udevacl) printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"%s\", ATTRS{model}==\"%s\", ENV{libsane_matched}=\"yes\"\n", scsiid->scsi_vendor_id, scsiid->scsi_product_id); else printf ("KERNEL==\"sg[0-9]*\", ATTRS{type}==\"3\", ATTRS{vendor}==\"%s\", ATTRS{model}==\"%s\", MODE=\"%s\", GROUP=\"%s\", ENV{libsane_matched}=\"yes\"\n", scsiid->scsi_vendor_id, scsiid->scsi_product_id, DEVMODE, DEVGROUP); scsiid = scsiid->next; } printf ("LABEL=\"libsane_scsi_rules_end\"\n"); if (mode == output_mode_udevacl) printf("\nENV{libsane_matched}==\"yes\", RUN+=\"/bin/setfacl -m g:%s:rw $env{DEVNAME}\"\n", DEVGROUP); else printf ("\nENV{libsane_matched}==\"yes\", MODE=\"664\", GROUP=\"scanner\"\n"); printf ("\nLABEL=\"libsane_rules_end\"\n"); } /* print libsane.rules for Linux udev */ static void print_udevhwdb_header (void) { print_header_comment (); printf ("#\n" "# udev rules file for supported USB and SCSI devices\n" "#\n" "# For the list of supported USB devices see /usr/lib/udev/hwdb.d/20-sane.hwdb\n" "#\n" "# The SCSI device support is very basic and includes only\n" "# scanners that mark themselves as type \"scanner\" or\n" "# SCSI-scanners from HP and other vendors that are entitled \"processor\"\n" "# but are treated accordingly.\n" "#\n"); printf ("# If your SCSI scanner isn't listed below, you can add it to a new rules\n" "# file under /etc/udev/rules.d/.\n" "#\n" "# If your scanner is supported by some external backend (brother, epkowa,\n" "# hpaio, etc) please ask the author of the backend to provide proper\n" "# device detection support for your OS\n" "#\n" "# If the scanner is supported by sane-backends, please mail the entry to\n" "# the sane-devel mailing list (sane-devel@alioth-lists.debian.net).\n" "#\n" ); } static void print_udevhwdb (void) { scsiid_type *scsiid = create_scsiids_table (); int i; print_udevhwdb_header (); printf("ACTION!=\"add\", GOTO=\"libsane_rules_end\"\n\n"); printf("# The following rule will disable USB autosuspend for the device\n"); printf("ENV{DEVTYPE}==\"usb_device\", ENV{libsane_matched}==\"yes\", TEST==\"power/control\", ATTR{power/control}=\"on\"\n\n"); printf ("SUBSYSTEMS==\"scsi\", GOTO=\"libsane_scsi_rules_begin\"\n"); printf ("GOTO=\"libsane_rules_end\"\n\n"); printf ("LABEL=\"libsane_scsi_rules_begin\"\n"); printf ("KERNEL!=\"sg[0-9]*\", GOTO=\"libsane_rules_end\"\n\n"); printf ("# Generic: SCSI device type 6 indicates a scanner\n"); printf ("ATTRS{type}==\"6\", ENV{libsane_matched}=\"yes\"\n\n"); printf ("# Some scanners advertise themselves as SCSI device type 3\n\n"); printf ("# Wildcard: for some Epson SCSI scanners\n"); printf ("ATTRS{type}==\"3\", ATTRS{vendor}==\"EPSON\", ATTRS{model}==\"SCANNER*\", ENV{libsane_matched}=\"yes\"\n\n"); while (scsiid) { manufacturer_model_type * name = scsiid->name; if (!scsiid->is_processor) { scsiid = scsiid->next; continue; } /* Wildcard for Epson scanners: vendor = EPSON, product = SCANNER* */ if ((strcmp(scsiid->scsi_vendor_id, "EPSON") == 0) && (strncmp(scsiid->scsi_product_id, "SCANNER", 7) == 0)) { scsiid = scsiid->next; continue; } i = 0; printf ("# "); while (name) { if ((name != scsiid->name) && (i > 0)) printf (" | "); printf ("%s", name->name); name = name->next; i++; /* * Limit the number of model names on the same line to 3, * as udev cannot handle very long lines and prints a warning * message while loading the rules files. */ if ((i == 3) && (name != NULL)) { printf("\n# "); i = 0; } } printf ("\n"); printf ("ATTRS{type}==\"3\", ATTRS{vendor}==\"%s\", ATTRS{model}==\"%s\", ENV{libsane_matched}=\"yes\"\n\n", scsiid->scsi_vendor_id, scsiid->scsi_product_id); scsiid = scsiid->next; } printf ("\nLABEL=\"libsane_rules_end\"\n"); } /* print /usr/lib/udev/hwdb.d/20-sane.conf for Linux hwdb */ static void print_hwdb_header (void) { print_header_comment (); printf ("#\n" "# hwdb file for supported USB devices\n" "#\n"); printf ("# If your scanner isn't listed below, you can add it to a new hwdb file\n" "# under /etc/udev/hwdb.d/.\n" "#\n" "# If your scanner is supported by some external backend (brother, epkowa,\n" "# hpaio, etc) please ask the author of the backend to provide proper\n" "# device detection support for your OS\n" "#\n" "# If the scanner is supported by sane-backends, please mail the entry to\n" "# the sane-devel mailing list (sane-devel@alioth-lists.debian.net).\n" "#" ); } static void print_hwdb (void) { usbid_type *usbid = create_usbids_table (); char *vendor_id; char *product_id; int i,j; print_hwdb_header (); while (usbid) { manufacturer_model_type * name = usbid->name; i = 0; printf ("\n# "); while (name) { if ((name != usbid->name) && (i > 0)) printf (" | "); printf ("%s", name->name); name = name->next; i++; /* * Limit the number of model names on the same line to 3, * as udev cannot handle very long lines and prints a warning * message while loading the rules files. */ if ((i == 3) && (name != NULL)) { printf("\n# "); i = 0; } } printf ("\n"); vendor_id = strdup(usbid->usb_vendor_id + 2); product_id = strdup(usbid->usb_product_id + 2); for(j = 0; j < 4; j++) { vendor_id[j] = toupper(vendor_id[j]); product_id[j] = toupper(product_id[j]); } printf ("usb:v%sp%s*\n libsane_matched=yes\n", vendor_id, product_id); free(vendor_id); free(product_id); usbid = usbid->next; } } static void print_plist (void) { usbid_type *usbid = create_usbids_table (); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\tdevice info version\n"); printf ("\t2.0\n"); printf ("\tusb\n"); printf ("\t\n"); printf ("\t\tIOUSBDevice\n"); printf ("\t\t\n"); while (usbid) { printf ("\t\t\t\n"); printf ("\t\t\t\tdevice type\n"); printf ("\t\t\t\tscanner\n"); printf ("\t\t\t\tproduct\n"); printf ("\t\t\t\t%s\n", usbid->usb_product_id); printf ("\t\t\t\tvendor\n"); printf ("\t\t\t\t%s\n", usbid->usb_vendor_id); printf ("\t\t\t\n"); usbid = usbid->next; } printf ("\t\t\n"); printf ("\t\n"); printf ("\n"); printf ("\n"); } static void print_hal (int new) { int i; SANE_Bool in_match; char *last_vendor; scsiid_type *scsiid = create_scsiids_table (); usbid_type *usbid = create_usbids_table (); printf ("\n"); printf ("\n"); printf (" \n"); printf (" \n"); printf (" \n"); printf (" \n"); printf (" \n"); last_vendor = ""; in_match = SANE_FALSE; while (scsiid) { manufacturer_model_type * name = scsiid->name; if (!scsiid->is_processor) { scsiid = scsiid->next; continue; } if (strcmp(last_vendor, scsiid->scsi_vendor_id) != 0) { if (in_match) printf (" \n"); printf (" \n", scsiid->scsi_vendor_id); last_vendor = scsiid->scsi_vendor_id; in_match = SANE_TRUE; } printf (" \n"); printf (" \n", scsiid->scsi_product_id); printf (" scanner\n"); printf (" \n"); scsiid = scsiid->next; } if (in_match) printf (" \n"); printf (" \n"); printf (" \n"); printf (" \n"); if (new) printf (" \n"); else printf (" \n"); last_vendor = ""; in_match = SANE_FALSE; while (usbid) { manufacturer_model_type * name = usbid->name; if (strcmp(last_vendor, usbid->usb_vendor_id) != 0) { if (in_match) printf (" \n"); printf (" \n", usbid->usb_vendor_id); last_vendor = usbid->usb_vendor_id; in_match = SANE_TRUE; } i = 0; printf (" \n"); printf (" \n", usbid->usb_product_id); printf (" scanner\n"); printf (" proprietary\n"); printf (" \n"); usbid = usbid->next; } if (in_match) printf (" \n"); printf (" \n"); printf (" \n"); printf ("\n"); } int main (int argc, char **argv) { program_name = strrchr (argv[0], '/'); if (program_name) ++program_name; else program_name = argv[0]; if (!get_options (argc, argv)) return 1; if (!read_files ()) return 1; switch (mode) { case output_mode_ascii: ascii_print_backends (); break; case output_mode_xml: xml_print_backends (); break; case output_mode_html_backends_split: html_print_backends_split (); break; case output_mode_html_mfgs: html_print_mfgs (); break; case output_mode_statistics: print_statistics (); break; case output_mode_usermap: print_usermap (); break; case output_mode_db: print_db (); break; case output_mode_udev: case output_mode_udevacl: print_udev (); break; case output_mode_udevhwdb: print_udevhwdb (); break; case output_mode_hwdb: print_hwdb (); break; case output_mode_plist: print_plist (); break; case output_mode_hal: print_hal (0); break; case output_mode_halnew: print_hal (1); break; default: DBG_ERR ("Unknown output mode\n"); return 1; } return 0; }