package scanner; # scanner.pm $Id$ # Yves Duret # Copyright (C) 2001-2002 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) = @_; $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/; 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); } } 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 configured() { my @res; # Run "scanimage -L", to find the scanners which are already working local *LIST; open LIST, "LC_ALL=C scanimage -L |"; while (my $line = ) { 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):/; # Store collected data push @res, { port => $port, val => { DESCRIPTION => $description, } }; } } close LIST; return @res; } sub detect { my @configured = @_; my @res; # Run "sane-find-scanner", this also detects USB scanners which only # work with libusb. local *DETECT; open DETECT, "LC_ALL=C sane-find-scanner -q |"; while (my $line = ) { my ($vendorid, $productid, $make, $model, $description, $port); 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) { # 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, } }; } 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; } return @res; } sub resolve_symlinks { # Check if a given file (either the pure filename or in a SANE device # string as ":") 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 = ) { 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 = ) { 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 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" }, 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; 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; } } 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 || $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; # id='n229' href='#n229'>229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
package services; # $Id$




#-######################################################################################
#- misc imports
#-######################################################################################

use strict;
use common;
use run_program;

use common;
use run_program;

sub description {
    my %services = (
alsa => N_("Launch the ALSA (Advanced Linux Sound Architecture) sound system"),
anacron => N_("Anacron is a periodic command scheduler."),
apmd => N_("apmd is used for monitoring battery status and logging it via syslog.
It can also be used for shutting down the machine when the battery is low."),
atd => N_("Runs commands scheduled by the at command at the time specified when
at was run, and runs batch commands when the load average is low enough."),
crond => N_("cron is a standard UNIX program that runs user-specified programs
at periodic scheduled times. vixie cron adds a number of features to the basic
UNIX cron, including better security and more powerful configuration options."),
fam => N_("FAM is a file monitoring daemon. It is used to get reports when files change.
It is used by GNOME and KDE"),
gpm => N_("GPM adds mouse support to text-based Linux applications such the
Midnight Commander. It also allows mouse-based console cut-and-paste operations,
and includes support for pop-up menus on the console."),
harddrake => N_("HardDrake runs a hardware probe, and optionally configures
new/changed hardware."),
httpd => N_("Apache is a World Wide Web server. It is used to serve HTML files and CGI."),
inet => N_("The internet superserver daemon (commonly called inetd) starts a
variety of other internet services as needed. It is responsible for starting
many services, including telnet, ftp, rsh, and rlogin. Disabling inetd disables
all of the services it is responsible for."),
ipchains => N_("Launch packet filtering for Linux kernel 2.2 series, to set
up a firewall to protect your machine from network attacks."),
keytable => N_("This package loads the selected keyboard map as set in
/etc/sysconfig/keyboard.  This can be selected using the kbdconfig utility.
You should leave this enabled for most machines."),
kheader => N_("Automatic regeneration of kernel header in /boot for
/usr/include/linux/{autoconf,version}.h"),