summaryrefslogtreecommitdiffstats
path: root/perl-install/c
Commit message (Expand)AuthorAgeFilesLines
* (pci_probe) wrap for readabilityThierry Vignaud2009-11-251-1/+2
* (usb_probe) wrap for readabilityThierry Vignaud2009-11-251-1/+3
* update drakx to get proper sysfs device path from usb devicesHerton Ronaldo Krzesinski2009-11-251-2/+2
* (pci_probe) better check for PCI Express capabilityThierry Vignaud2009-09-301-2/+2
* (pci_probe__real,c::pci_probe) retrieve PCI revision (#42576)Thierry Vignaud2009-09-231-2/+2
* diskdrake: fix for libparted 1.9 (#52991)Pascal Terjan2009-08-271-1/+1
* handle partition starting after 1ToPascal Terjan2009-04-211-1/+1
* diskdrake: don't crash on invalid partition tablePascal Terjan2009-02-131-31/+41
* Rewrite gpt support with libpartedPascal Terjan2009-01-272-1/+140
* fix fd leak when detecting network driver (mostly notable in network center a...Olivier Blin2008-04-181-0/+1
* fix minor fd leakOlivier Blin2008-04-181-0/+1
* fix build of wireless binding with latest kernel headersOlivier Blin2007-07-171-0/+1
* re-sync after the big svn lossPascal Rigaux2007-04-253-108/+15
* remove unused silo&sbus&prom stuff (was for sparc, but untouched for more tha...Pascal Rigaux2006-03-034-1063/+1
* replacing double with NV as suggested by rafael:Pascal Rigaux2006-01-121-1/+1
* use BLKGETSIZE64 to allow detecting partitions bigger than 2TB, and usePascal Rigaux2006-01-121-4/+6
* look for "NR_CPUS limit of 1 reached" instead of looking MP tables by hand, o...Pascal Rigaux2006-01-107-859/+1
* - i had foolishly removed c::iconv whereas it was used still used by c::from_...Pascal Rigaux2005-12-152-10/+0
* use Locale::gettext for dgettext, bindtextdomain and bind_textdomain_codeset ...Pascal Rigaux2005-12-151-17/+0
* c::iconv() is not used anymore (and if needed, there is Locale::gettext::icon...Pascal Rigaux2005-12-151-32/+0
* use vol_id to find device type (fs_type and pt_type)Pascal Rigaux2005-10-182-22/+1
* use vol_id to get filesystem label (we only handled the equivalenet of e2label)Pascal Rigaux2005-08-291-17/+0
* map more closely to dmidecode behaviour's, aka mmap(/dev/mem) and find/readGwenolé Beauchesne2005-08-091-66/+89
* revert, that's not needed after allOlivier Blin2005-07-211-1/+0
* add volume_set_idOlivier Blin2005-07-211-0/+1
* add setlocate() and LC_COLLATE for common.pmThierry Vignaud2005-06-301-0/+9
* rename setlocale() as init_setlocale() since it's more meaninfull regarding i...Thierry Vignaud2005-06-301-1/+1
* (strftime) add it thus avoiding to load bloaty POSIX in net_appletThierry Vignaud2005-06-301-0/+22
* Remove rpm include dir from Makefile_cRafael Garcia-Suarez2005-06-301-1/+1
* do not link anymore with librpm since perl-URPM is used for that (saves 6.1MbThierry Vignaud2005-06-301-1/+0
* Remove dependency of c::stuff on rpmlibRafael Garcia-Suarez2005-06-301-26/+0
* Remove function not used (and provided by perl-URPM anyway)Rafael Garcia-Suarez2005-06-301-6/+1
* remove unused codePascal Rigaux2005-06-031-9/+3
* remove dobleThierry Vignaud2005-06-031-1/+1
* add O_CREATThierry Vignaud2005-06-031-1/+1
* add O_WRONLY & O_RDWRThierry Vignaud2005-06-031-1/+1
* add commented line to help debugging pci_probePascal Rigaux2005-05-131-0/+1
* add enable_net_deviceOlivier Blin2005-03-311-0/+19
* - add dmi_probe()Pascal Rigaux2005-03-151-0/+19
* 64-bit fixes for x86_64Gwenolé Beauchesne2004-10-041-30/+35
* prototypes are dangerousPascal Rigaux2004-09-101-1/+1
* max() is not defined, inline itPascal Rigaux2004-09-091-1/+3
* add dmi type 6 in detection memory sizeNicolas Planel2004-09-091-2/+27
* revert my last bad commitNicolas Planel2004-08-171-7/+102
* rework smp-dmi, add dmi memory detection suppportNicolas Planel2004-08-172-145/+115
* fix buildThierry Vignaud2004-08-021-2/+2
* always include all funcs (these're needed anyway by Move and GlobeTrotter)Thierry Vignaud2004-07-292-11/+8
* workaround blind gwenoleThierry Vignaud2004-07-061-1/+1
* workaround build with new kernelsThierry Vignaud2004-07-061-0/+1
* drop useless linkage since X11 stuff was split out of c moduleThierry Vignaud2004-06-271-1/+0
'>580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
package scanner;
# scanner.pm $Id$
# Yves Duret <yduret at mandrakesoft.com>
# Till Kamppeter <till at mandrakesoft.com>
# Copyright (C) 2001-2004 Mandrakesoft
#
# 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, 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.
#
# pbs/TODO:
# - scsi mis-configuration (should work better now)
# - devfs use dev_is_devfs()
# - with 2 scanners same manufacturer -> will overwrite previous conf -> only 1 conf !! (should work now)
# - lp: see printerdrake
# - install: prefix --> done (partially)

use common;
use detect_devices;
use log;
use handle_configs;

my $sanedir = "$::prefix/etc/sane.d";
my $scannerDBdir = "$::prefix$ENV{SHARE_PATH}/ldetect-lst";
our $scannerDB = readScannerDB("$scannerDBdir/ScannerDB");

sub confScanner {
    my ($model, $port, $vendor, $product, $firmware) = @_;
    $port ||= detect_devices::dev_is_devfs() ? "$::prefix/dev/usb/scanner0" : "$::prefix/dev/scanner";
    my $a = $scannerDB->{$model}{server};
    #print "file:[$a]\t[$model]\t[$port]\n| ", (join "\n| ", @{$scannerDB->{$model}{lines}}),"\n";
    my @driverconf = cat_("$sanedir/$a.conf");
    my @configlines = @{$scannerDB->{$model}{lines}};
    foreach my $line (@configlines) {
	$line =~ s/\$DEVICE/$port/g if $port;
	next if $line =~ /\$DEVICE/;
	$line =~ s/\$VENDOR/$vendor/g if $vendor;
	next if $line =~ /\$VENDOR/;
	$line =~ s/\$PRODUCT/$product/g if $product;
	next if $line =~ /\$PRODUCT/;
	$line =~ s/\$FIRMWARE/$firmware/g if $firmware;
	next if $line =~ /\$FIRMWARE/;
	my $linetype;
	if ($line =~ /^(\S*)LINE\s+(.*?)$/) {
	    $linetype = $1;
	    $line = $2;
	}
	next if !$line;
	if (!$linetype ||
	    ($linetype eq "USB" && ($port =~ /usb/i || $vendor)) ||
	    ($linetype eq "PARPORT" && !$vendor &&
	     $port =~ /(parport|pt_drv|parallel)/i) ||
	    ($linetype eq "SCSI" && !$vendor &&
	     $port =~ m!(/sg|scsi|/scanner)!i)) {
	    handle_configs::set_directive(\@driverconf, $line, 1);
	} elsif ($linetype eq "FIRMWARE" && $firmware) {
	    handle_configs::set_directive(\@driverconf, $line, 0);
	}
    }
    output("$sanedir/$a.conf", @driverconf);
    add2dll($a);
}

sub add2dll {
    return if member($_[0], chomp_(cat_("$sanedir/dll.conf")));
    my @dllconf = cat_("$sanedir/dll.conf");
    handle_configs::add_directive(\@dllconf, $_[0]);
    output("$sanedir/dll.conf", @dllconf);
}

sub setfirmware {
    my ($backend, $firmwareline) = @_;
    my @driverconf = cat_("$sanedir/$backend.conf");
    handle_configs::set_directive(\@driverconf, $firmwareline, 0);
    output("$sanedir/$backend.conf", @driverconf);
}

sub installfirmware {
    # Install the firmware file in /usr/share/sane/firmware
    my ($firmware, $backend) = @_;
    return "" if !$firmware;
    # Install firmware
    run_program::rooted($::prefix, "mkdir", "-p",
			"/usr/share/sane/firmware") || do {
			    $in->ask_warn('Scannerdrake',
					  N("Could not create directory /usr/share/sane/firmware!"));
			    return "";
			};
    # Link /usr/share/sane/firmware to /usr/share/sane/<backend name> as
    # some backends ignore the supplied absolute path to the firmware file
    # and always search their own directory
    if ($backend) {
	run_program::rooted($::prefix, "ln", "-sf",
			    "/usr/share/sane/firmware",
			    "/usr/share/sane/$backend") || do {
				$in->ask_warn('Scannerdrake',
					      N("Could not create link /usr/share/sane/%s!", $backend));
				return "";
			    };
    }
    run_program::rooted($::prefix, "cp", "-f", "$firmware",
			"/usr/share/sane/firmware") || do {
			    $in->ask_warn('Scannerdrake',
					  N("Could not copy firmware file %s to /usr/share/sane/firmware!", $firmware));
			    return "";
			};
    $firmware =~ s!^(.*)(/[^/]+)$!/usr/share/sane/firmware$2!;
    run_program::rooted($::prefix, "chmod", "644",
			$firmware) || do {
			    $in->ask_warn('Scannerdrake',
					  N("Could not set permissions of firmware file %s!", $firmware));
			    return "";
			};
    return $firmware;
}

sub configured() {
    my @res;
    my $parportscannerfound = 0;
    # Run "scanimage -L", to find the scanners which are already working
    local *LIST;
    open LIST, "LC_ALL=C scanimage -L |";
    while (my $line = <LIST>) {
	if ($line =~ /^\s*device\s*`([^`']+)'\s+is\s+a\s+(\S.*)$/) {
	    # Extract port and description
	    my $port = $1;
	    my $description = $2;
	    # Remove duplicate scanners appearing through saned and the
	    # "net" backend
	    next if $port =~ /^net:(localhost|127.0.0.1):/;
	    # Is the scanner hooked to a parallel or serial port?
	    if ($port =~ /(parport|pt_drv|parallel|ttys)/i) {
		$parportscannerfound = 1;
	    }
	    # Determine which SANE backend the scanner in question uses
	    $port =~ /^([^:]+):/;
	    my $backend = $1;
	    # Does the scanner need a firmware file
	    my $firmwareline = firmwareline($backend);
	    # Store collected data
	    push @res, { 
		port => $port, 
		val => { 
		    DESCRIPTION => $description,
		    ($backend ? ( BACKEND => $backend ) : ()),
		    ($firmwareline ? 
		     ( FIRMWARELINE => $firmwareline ) : ()),
		}
	    }
	}
    }
    close LIST;
    # We have a parallel port scanner, make it working for non-root users
    nonroot_access_for_parport($parportscannerfound);
    return @res;
}

sub nonroot_access_for_parport {

    # This function configures a non-root access for parallel port
    # scanners by running saned as root, esporting the scanner to
    # localhost and letting the user's frontend use the "net" backend
    # to access the scanner through the loopback network device.

    # See also
    # http://www.linuxprinting.org/download/digitalimage/Scanning-as-Normal-User-on-Wierd-Scanner-Mini-HOWTO.txt

    # Desired state of this facility: 1: Enable, 0: Disable
    my ($enable) = @_;
    # Is saned running?
    my $sanedrunning = services::starts_on_boot("saned");
    # Is the "net" SANE backend active
    my $netbackendactive = grep { /^\s*net\s*$/ }
      cat_("/etc/sane.d/dll.conf");
    # Set this to 1 to tell the caller that the list of locally available
    # scanners has changed (Here if the SANE client configuration has
    # changed)
    my $changed = 0;
    my $importschanged = 0;
    if ($enable) {
	# Enable non-root access
	
	# Install/start saned
	if (!$sanedrunning) {
	    # Make sure saned and xinetd is installed and 
	    # running
	    if (!files_exist('/usr/sbin/xinetd',
			     '/usr/sbin/saned')) {
		if (!$in->do_pkgs->install('xinetd', 'saned')) {
		    $in->ask_warn(N("Scannerdrake"),
				  N("Could not install the packages needed to share your scanner(s).") . " " .
				  N("Your scanner(s) will not be available for non-root users."));
		}
		return 0;
	    }
	}

	# Modify /etc/xinetd.d/saned to let saned run as root
	my @sanedxinetdconf = cat_("/etc/xinetd.d/saned");
	( s/(user\s*=\s*).*$/$1root/ ) foreach @sanedxinetdconf;
	( s/(group\s*=\s*).*$/$1root/ ) foreach @sanedxinetdconf;
	output("/etc/xinetd.d/saned", @sanedxinetdconf);

	# Read list of hosts to where to export the local scanners
	my @exports = cat_("/etc/sane.d/saned.conf");
	# Read list of hosts from where to import scanners
	my @imports = cat_("/etc/sane.d/net.conf");
	# Add "localhost" to the machines which saned exports
	handle_configs::set_directive(\@exports, "localhost")
	    if !member("localhost\n", @exports);
	# Add "localhost" to the machines which "net" imports
	handle_configs::set_directive(\@imports, "localhost")
	    if !member("localhost\n", @imports);
	# Write /etc/sane.d/saned.conf
	output("/etc/sane.d/saned.conf", @exports);
	# Write /etc/sane.d/net.conf
	output("/etc/sane.d/net.conf", @imports);

	# Make sure that the "net" backend is active
	scanner::add2dll("net");
	
	# (Re)start saned and make sure that it gets started on
	# every boot
	services::start_service_on_boot("saned");
	services::start_service_on_boot("xinetd");
	services::restart("xinetd");

    } else {
	# Disable non-root access

	if (-r "/etc/xinetd.d/saned") {
	    # Modify /etc/xinetd.d/saned to let saned run as saned
	    my @sanedxinetdconf = cat_("/etc/xinetd.d/saned");
	    ( s/(user\s*=\s*).*$/$1saned/ ) foreach @sanedxinetdconf;
	    ( s/(group\s*=\s*).*$/$1saned/ ) foreach @sanedxinetdconf;
	    output("/etc/xinetd.d/saned", @sanedxinetdconf);
	    # Restart xinetd
	    services::restart("xinetd") if $sanedrunning;
	}
    }

    return 1;
}

sub detect {
    my @configured = @_;
    my @res;
    # Run "sane-find-scanner", this also detects USB scanners which only
    # work with libusb.

    my @devices = detect_devices::probeall();

    local *DETECT;
    open DETECT, "LC_ALL=C sane-find-scanner -q |";
    while (my $line = <DETECT>) {
	my ($vendorid, $productid, $make, $model, $description, $port, $driver);
	if ($line =~ /^\s*found\s+USB\s+scanner/i) {
	    # Found an USB scanner
	    if ($line =~ /vendor=(0x[0-9a-f]+)[^0-9a-f\[]+[^\[]*\[([^\[\]]+)\].*prod(|uct)=(0x[0-9a-f]+)[^0-9a-f\[]+[^\[]*\[([^\[\]]+)\]/) {
		# Scanner connected via libusb
		$vendorid = $1;
		$make = $2;
		$productid = $4;
		$model = $5;
		$description = "$make|$model";
	    } elsif ($line =~ /vendor=(0x[0-9a-f]+)[^0-9a-f]+.*prod(|uct)=(0x[0-9a-f]+)[^0-9a-f]+/) {
		# Scanner connected via scanner.o kernel module
		$vendorid = $1;
		$productid = $3;
	    }
	    if ($vendorid && $productid) {
		my ($vendor) = ($vendorid =~ /0x([0-9a-f]+)/);
		my ($id) = ($productid =~ /0x([0-9a-f]+)/);
		my ($device) = grep { sprintf("%04x", $_->{vendor}) eq $vendor && sprintf("%04x", $_->{id}) eq $id } @devices;

		if ($device) {
              $driver = $device->{driver}
		} else {
              warn "i failled to lookupp $vendorid && $productid";
		}
                 
		# We have vendor and product ID, look up the scanner in
		# the usbtable
		foreach my $entry (cat_("$scannerDBdir/usbtable")) {
		    if ($entry =~ 
			/^\s*$vendorid\s+$productid\s+.*\"([^\"]+)\"\s*$/) {
			$description = $1;
			$description =~ s/Seiko\s+Epson/Epson/i;
			if ($description =~ /^([^\|]+)\|(.*)$/) {
			    $make = $1;
			    $model = $2;
			}
			last;
		    }
		}
	    }
	} elsif ($line =~ /^\s*found\s+SCSI/i) {
	    # SCSI scanner
	    if ($line =~ /\"([^\"\s]+)\s+([^\"]+?)\s+([^\"\s]+)\"/) {
		$make = $1;
		$model = $2;
		$description = "$make|$model";
	    }
	} else {
	    # Comment line in output of "sane-find-scanner"
	    next;
	}
	# The Alcatel Speed Touch internet scanner is not supported by
	# SANE
	next if $description =~ /Alcatel.*Speed.*Touch|Camera|ISDN|ADSL/i;
	# Extract port
	$port = $1 if $line =~ /\s+(\S+)\s*$/;
	# Check for duplicate (scanner.o/libusb)
	if ($port =~ /^libusb/) {
	    my $duplicate = 0;
	    foreach (@res) {
		if ($_->{val}{vendor} eq $vendorid &&
		    $_->{val}{id} eq $productid &&
		    $_->{port} =~ /dev.*usb.*scanner/ &&
		    !defined($_->{port2})) {
		    # Duplicate entry found, merge the entries
		    $_->{port2} = $port;
		    $_->{val}{MANUFACTURER} ||= $make;
		    $_->{val}{MODEL} ||= $model;
		    $_->{val}{DESCRIPTION} ||= $description;
		    $duplicate = 1;
		    last;
		}
	    }
	    next if $duplicate;
	}
	# Store collected data
	push @res, { 
	    port => $port, 
	    val => { 
		CLASS => 'SCANNER',
		MODEL => $model,
		MANUFACTURER => $make,
		DESCRIPTION => $description,
		id => $productid,
		vendor => $vendorid,
		driver => $driver,
	    } 
	};
    }
    close DETECT;
    if (@configured) {
	# Remove scanners which are already working
	foreach my $d (@res) {
	    my $searchport1 =
		handle_configs::searchstr(resolve_symlinks($d->{port}));
	    my $searchport2 =
		handle_configs::searchstr(resolve_symlinks($d->{port2}));
	    foreach my $c (@configured) {
		my $currentport = resolve_symlinks($c->{port});
		if ($currentport =~ /$searchport1$/ ||
		    $searchport2 && $currentport =~ /$searchport2$/) {
		    $d->{configured} = 1;
		    last;
		}
	    }
	}
	@res = grep { ! $_->{configured} } @res;
    }
    # blacklist device that have a driver b/c of buggy sane-find-scanner:
    return grep { member($_->{driver}, qw(scanner unknown)) } @res;
}

sub resolve_symlinks {

    # Check if a given file (either the pure filename or in a SANE device
    # string as "<prefix>:<file>") is a symlink, if so expand the link.
    # If the new file name is a link, expand again, until finding the
    # physical file.
    my ($file) = @_;
    my $prefix = "";
    if ($file =~ m!^([^/]*)(/.*)$!) {
	$prefix = $1;
	$file = $2;
    } else {
	return $file;
    }
    while (1) {
	my $ls = `ls -l $file`;
	if ($ls =~ m!\s($file)\s*\->\s*(\S+)\s*$!) {
	    my $target = $2;
	    if ($target !~ m!^/! && $file =~ m!^(.*)/[^/]+$!) {
		$target = "$1/$target";
	    }
	    $file = $target;
	} else {
	    last;
	}
    }
    return $prefix . $file;
}

sub get_usb_ids_for_port {
    my ($port) = @_;
    local *DETECT;
    if ($port =~ /^\s*libusb:(\d+):(\d+)\s*$/) {
	# Use "lsusb" to find the USB IDs
	open DETECT, "LC_ALL=C lsusb -s $1:$2 |";
	while (my $line = <DETECT>) {
	    if ($line =~ /ID\s+([0-9a-f]+):(0x[0-9a-f]+)($|\s+)/) {
		# Scanner connected via scanner.o kernel module
		return "0x$1", "0x$2";
		last;
	    }
	}
    } else {
	# Run "sane-find-scanner" on the port
	open DETECT, "LC_ALL=C sane-find-scanner -q $port |";
	while (my $line = <DETECT>) {
	    if ($line =~ /^\s*found\s+USB\s+scanner/i) {
		if ($line =~ /vendor=(0x[0-9a-f]+)[^0-9a-f]+.*prod(|uct)=(0x[0-9a-f]+)[^0-9a-f]+/) {
		    # Scanner connected via scanner.o kernel module
		    return $1, $3;
		}
	    }
	}
    }
}

sub readconfiglinetemplates {
    # Read templates for configuration file lines
    my %configlines;
    my $backend;
    foreach my $line (cat_("$scannerDBdir/scannerconfigs")) {
	chomp $line;
	if ($line =~ /^\s*SERVER\s+(\S+)\s*$/) {
	    $backend = $1;
	} elsif ($backend) {
	    push @{$configlines{$backend}}, $line;
	}
    }
    return \%configlines;
}

sub firmwareline {
    # Determine whether the given SANE backend supports a firmware file
    # and return the line needed in the config file
    my ($backend) = @_;
    # Read templates for configuration file lines
    my %configlines = %{readconfiglinetemplates()};
    # Does the backend support a line for the firmware?
    my @firmwarelines = (grep { s/^FIRMWARELINE // } @{$configlines{$backend}});
    return join("\n", @firmwarelines);
}

sub readScannerDB {
    my ($file) = @_;
    my ($card, %cards);

    my $F = common::openFileMaybeCompressed($file);

    my ($lineno, $cmd, $val) = 0;
    my $fs = {
        LINE => sub { push @{$card->{lines}}, "LINE $val" },
        SCSILINE => sub { push @{$card->{lines}}, "SCSILINE $val" },
        USBLINE => sub { push @{$card->{lines}}, "USBLINE $val" },
        PARPORTLINE => sub { push @{$card->{lines}}, "PARPORTLINE $val" },
        FIRMWARELINE => sub { push @{$card->{lines}}, "FIRMWARELINE $val" },
	NAME => sub {
	    #$cards{$card->{type}} = $card if ($card and !$card->{flags}{unsupported});
	    $cards{$card->{type}} = $card if $card;
	    $val =~ s/Seiko\s+Epson/Epson/i;
	    $card = { type => $val };
	},
	SEE => sub {
	    $val =~ s/Seiko\s+Epson/Epson/i;
	    my $c = $cards{$val} or die "Error in database, invalid reference $val at line $lineno";

	    push @{$card->{lines}}, @{$c->{lines} || []};
	    add2hash($card->{flags}, $c->{flags});
	    add2hash($card, $c);
	},
	ASK => sub { $card->{ask} = $val },
	SERVER => sub { $card->{server} = $val },
	DRIVER => sub { $card->{driver} = $val },
	UNSUPPORTED => sub { $card->{flags}{unsupported} = 1 },
	COMMENT => sub {},
    };

    local $_;
    while (<$F>) { $lineno++;
	s/\s+$//;
	/^#/ and next;
	/^$/ and next;
	/^END/ and do { $cards{$card->{type}} = $card if $card; last };
	($cmd, $val) = /(\S+)\s*(.*)/ or next; #log::l("bad line $lineno ($_)"), next;
	my $f = $fs->{$cmd};
	$f ? $f->() : log::l("unknown line $lineno ($_)");
    }
    \%cards;
}

sub updateScannerDBfromUsbtable() {
    substInFile { s/^END// } "ScannerDB";
    my $to_add = "# generated from usbtable by scannerdrake\n";
    foreach (cat_("$ENV{SHARE_PATH}/ldetect-lst/usbtable")) {
	my ($vendor_id, $product_id, $mod, $name) = chomp_(split /\s/,$_,4);
	next if $mod ne '"scanner"';
	$name =~ s/\"(.*)\"$/$1/;
	if (member($name, keys %$scanner::scannerDB)) {
	    print "#[$name] already in ScannerDB!\n";
	    next;
	}
	$to_add .= "NAME $name\nDRIVER USB\nCOMMENT usb $vendor_id $product_id\nUNSUPPORTED\n\n";
    }
    $to_add .= "END\n";

    append_to_file("ScannerDB", $to_add);
}

sub updateScannerDBfromSane {
    my ($sanesrcdir) = @_;
    substInFile { s/^END// } "ScannerDB";

    my $to_add = "# generated from Sane by scannerdrake\n";
    # for compat with our usbtable
    my $sane2DB = { 
		   "Acer" => "Acer Peripherals Inc.",
		   "AGFA" => "AGFA-Gevaert NV",
		   "Agfa" => "AGFA-Gevaert NV",
		   "Epson" => "Epson Corp.",
		   "Fujitsu Computer Products of America" => "Fujitsu",
		   "HP" => sub { $_[0] =~ s/HP\s/Hewlett-Packard|/; $_[0] =~ s/HP4200/Hewlett-Packard|ScanJet 4200C/; $_[0] },
		   "Hewlett-Packard" => sub { $_[0] =~ s/HP 3200 C/Hewlett-Packard|ScanJet 3200C/ or $_[0] = "Hewlett-Packard|$_[0]"; $_[0] },
		   "Kodak" => "Kodak Co.",
		   "Mustek" => "Mustek Systems Inc.",
		   "NEC" => "NEC Systems",
		   "Nikon" => "Nikon Corp.",
		   "Plustek" => "Plustek, Inc.",
		   "Primax" => "Primax Electronics",
		   "Siemens" => "Siemens Information and Communication Products",
		   "Trust" => "Trust Technologies",
		   "UMAX" => "Umax",
		   "Vobis/Highscreen" => "Vobis",
		  };

    # Read templates for configuration file lines
    my %configlines = %{readconfiglinetemplates()};

    foreach my $ff (glob_("$sanesrcdir/doc/descriptions/*.desc"), glob_("$sanesrcdir/doc/descriptions-external/*.desc"), "UNSUPPORTED") {
	my $f = $ff;
	# unsupported.desc must be treated separately, as the list of
	# unsupported scanners in SANE is out of date.
	next if $f =~ /unsupported.desc$/;
	# Treat unsupported.desc in the end
	$f = "$sanesrcdir/doc/descriptions/unsupported.desc" if
	    ($f eq "UNSUPPORTED");
	my $F = common::openFileMaybeCompressed($f);
	$to_add .= "\n# from $f";
	my ($lineno, $cmd, $val) = 0;
	my ($name, $intf, $comment, $mfg, $backend);
	my $fs = {
		  backend => sub { $backend = $val },
		  mfg => sub { $mfg = $val; $name = undef },#bug when a new mfg comes. should called $fs->{ $name }(); but ??
		  model => sub {
		      unless ($name) { $name = $val; return }
		      $name = member($mfg, keys %$sane2DB) ?
			ref($sane2DB->{$mfg}) ? $sane2DB->{$mfg}($name) : "$sane2DB->{ $mfg }|$name" : "$mfg|$name";
		      # When adding the unsupported scanner models, check
		      # whether the model is not already supported. To
		      # compare the names ignore upper/lower case.
		      my $searchname = quotemeta($name);
		      if (($backend =~ /unsupported/i) &&
			  ($to_add =~ /^NAME $searchname$/im)) {
			  $to_add .= "# $name already supported!\n";
		      } else {
			  # SANE bug: "snapscan" calls itself "SnapScan"
			  $backend =~ s/SnapScan/snapscan/g;
			  $to_add .= "\nNAME $name\nSERVER $backend\nDRIVER $intf\n";
			  # Go through the configuration lines of
			  # this backend and add what is needed for the
			  # interfaces of this scanner
			  foreach my $line (@{$configlines{$backend}}) {
			      my $i = $1 if $line =~ /^\s*(\S*?)LINE/;
			      if (!$i || $i eq "FIRMWARE" || 
				  $intf =~ /$i/i) {
				  $to_add .= "$line\n";
			      }
			  }
			  if ($backend =~
			      /(unsupported|mustek_pp|gphoto2)/i) {
			      $to_add .= "UNSUPPORTED\n";
			  }
			  $to_add .= "COMMENT $comment\n" if $comment;
			  $comment = undef; 
		      }
		      $name = $val;
		  },
		  interface => sub { $intf = $val },
		  comment => sub { $comment = $val },
		 };
	local $_;
	while (<$F>) { $lineno++;
		       s/\s+$//;
		       /^;/ and next;
		       ($cmd, $val) = /:(\S+)\s*\"([^;]*)\"/ or next; #log::l("bad line $lineno ($_)"), next;
		       my $f = $fs->{$cmd};
		       $f ? $f->() : log::l("unknown line $lineno ($_)");
		   }
	$fs->{model}(); # the last one
    }
    $to_add .= "\nEND\n";
    append_to_file("ScannerDB", $to_add);
}

1; #