#!/usr/bin/perl
# chkconfig: 2345 59 61
# description: ptal-init is the init script and device setup utility \
#              for the HP OfficeJet Linux driver.
# Should be started before and stopped after your print spooler (lpd or CUPS).

# scripts/ptal-init.  Generated from ptal-init.in by configure.

# Copyright (C) 2001-2003 Hewlett-Packard Company
#
# 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
# is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
# NON-INFRINGEMENT.  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.
 
# Original author: David Paschal

use strict;


###########################################################################
# In case of difficulty you might need to modify the following definitions.
# If you do, then please notify hpoj-devel@lists.sourceforge.net so your
# system can be better supported in a future version of the hpoj software.
###########################################################################

# If necessary, add to or rearrange these lists of possible wildcard patterns
# for parallel and USB printer device nodes on your system.
my @parWildcards=(
	"/dev/lp[0-9]*",
	"/dev/lpt[0-9]*"
	);
my @usbWildcards=(
	"/dev/usb/lp[0-9]*",
	"/dev/usblp[0-9]*"
	);
# These wildcards are only used as a last resort for the ptal-printd
# "-like" template.
my @moreWildcards=(
	"/dev/ulpt[0-9]*"
	);

###########################################################################
# You probably don't need to modify anything after this point in the file.
###########################################################################

# Workaround for Perl+UTF8 bug on RedHat 9:
exec 'env', 'LANG=C', $0, @ARGV unless $ENV{"LANG"} eq "C";

# Ensure that anybody can read the files and directories we create:
umask(umask()&0222);


# Configuration file keys:
my $cfgstrVersion="init.version";
my $cfgstrMlcdAppend="init.mlcd.append";
my $cfgstrPrintdStart="init.printd.start";
my $cfgstrPrintdAppend="init.printd.append";
my $cfgstrPhotodStart="init.photod.start";
my $cfgstrPhotodAppend="init.photod.append";


# Global variables:
my $fullname=$0;
$fullname=~/([^\/]*)$/;
my $basename=$1;
if ($basename=~/^[A-Z]\d+/) {
	$basename=$';
}
my $retcode=0;
my $prefix="/usr";
my $etcPtal="/etc/ptal";
my $etcPtalDefaults="$etcPtal/defaults";
my $etcPtalDefaultDevice="$etcPtal/default-device";
my $etcPtalPrintdLike="$etcPtal/ptal-printd-like";
my $varLock="/var/lock";
my $varLockSubsys="$varLock/subsys";
my $osPlatform=`uname -s 2>/dev/null`;
chomp $osPlatform;
my $linuxVersion=($osPlatform eq 'Linux' ? `uname -r 2>/dev/null` : '');
$linuxVersion=~s/^(\s*)(\d+\.\d+)(\..*)$/$2/;
chomp $linuxVersion;
my $usbprintermodule = ($linuxVersion eq '2.6' ? "usblp" : "printer");
my %devnames;
my %obsoleteDevnames;
my %configInfo;
my $defaultDevice;
my $parWildcard;
my $ptalPrintdLike;
my $suggestedParDevnode="/dev/lp0";
my $pleaseBeQuiet;
my $pleaseBeVerbose;
my @foundParports;
my $askYNSuffix;
my $currentFileFormatVersion=2;
my $forcelibusb;	# undef by default.
my $forcesmp;		# undef by default.


sub pickWildcardPattern {
	my (@patterns)=@_;
	my ($pattern);

	foreach $pattern (@patterns) {
		my (@filenames)=glob($pattern);
		my $filename;
		# Uncomment to debug file globbing:
		# foreach $filename (@filenames) {
		#	print "pickWildcardPattern: $pattern -> $filename\n";
		# }
		if (scalar(@filenames)) {
			return $pattern;
		}
	}

	return undef;
}

sub setupVariables {
	# Turn off buffering of prints that lack newlines.
	select(STDOUT);
	$|=1;

	# Set up PATHs and wildcard patterns.
	$ENV{"PATH"}="$prefix/bin:$prefix/sbin:/sbin:/usr/sbin:".$ENV{"PATH"};
	$ENV{"LD_LIBRARY_PATH"}="$prefix/lib:".$ENV{"LD_LIBRARY_PATH"};
	$parWildcard=&pickWildcardPattern(@parWildcards);
	my $usbWildcard=&pickWildcardPattern(@usbWildcards);
	my $moreWildcard=&pickWildcardPattern(@moreWildcards);
	if (defined($parWildcard)) {
		($ptalPrintdLike)=<${parWildcard}>;
		$suggestedParDevnode=$ptalPrintdLike;
	} elsif (defined($usbWildcard)) {
		($ptalPrintdLike)=<${usbWildcard}>;
	} elsif (defined($moreWildcard)) {
		($ptalPrintdLike)=<${moreWildcard}>;
	}
}

sub stopDaemons {
	system("killall ptal-photod ptal-printd ptal-mlcd >/dev/null 2>/dev/null");
}

sub cleanup {
	my ($varRunPrefix,$path,@dirs,$dir);

	# Delete and recreate the daemon directories in the new FSH-approved
	# location.
	$varRunPrefix="/var/run";
	system("rm -rf /dev/ptal-mlcd /dev/ptal-printd $varRunPrefix/ptal-mlcd $varRunPrefix/ptal-printd >/dev/null 2>/dev/null");
	foreach $path ("$varRunPrefix/ptal-mlcd","$varRunPrefix/ptal-printd",$etcPtal) {
		@dirs=split(/\/+/,$path);
		$dir="";
		while ($#dirs>=$[) {
			$dir.="/".shift(@dirs);
			if (! -d $dir && !mkdir($dir,0755)) {
				print "*** Unable to create directory $dir ($!)!\n";
			}
		}
	}
	# This may fail in the case of a read-only /dev filesystem,
	# but that's OK, since it's only for backwards compatibility:
	symlink "$varRunPrefix/ptal-printd","/dev/ptal-printd";

	# Set up and use a more persistent pattern for "ptal-printd -like".
	if (!-e $etcPtalPrintdLike && defined($ptalPrintdLike) &&
	    -e $ptalPrintdLike) {
		my ($dev,$ino,$mode,$nlink,$uid,$gid)=stat($ptalPrintdLike);
		if (defined($gid) && open(LIKE,">$etcPtalPrintdLike")) {
			$_=`date 2>/dev/null`;
			chomp;
			print LIKE
"# ptal-init tells ptal-printd to use this file as a template for the\n".
"# security settings (mode, owner and group) for the named pipes in\n".
"# \"$varRunPrefix/ptal-printd\".  The actual contents of this file are\n".
"# ignored.  It was originally created on $_\n".
"# based on \"$ptalPrintdLike\".\n";
			chmod($mode,$etcPtalPrintdLike);
			chown($uid,$gid,$etcPtalPrintdLike);
			close LIKE;
		}
	}
	if (-e $etcPtalPrintdLike) {
		$ptalPrintdLike=$etcPtalPrintdLike;
	}
}

sub printSeparator {
	print
"\n".
"----------------------------------------------------------------------\n".
"\n";
}

sub ask {
	my ($response);

	$response=<STDIN>;
	if ($response eq "") {
		print "\n";
		return undef;
	}
	chomp $response;
	$response=~/^\s*/;
	$'=~/\s*$/;
	return $`;
}

sub askYN {
	my ($question,$default)=@_;
	my ($response);

	undef $askYNSuffix;
	print "$question (".($default?"[y]/n":"y/[n]").")?  ";
	$response=&ask;
	if (!defined($response)) {
		return undef;
	}
	if ($response=~/^\s*[Yy]/) {
		if ($'=~/^\S*\s+(-.+)$/) {
			$askYNSuffix=$1;
		}
		return 1;
	}
	if ($response=~/^\s*[Nn]/) {
		return 0;
	}
	return $default;
}

sub getDeviceCount {
	return (scalar(keys(%obsoleteDevnames))+scalar(keys(%devnames)));
}

sub deviceIsDefined {
	my ($devname,$strict)=@_;

	return (defined($devnames{$devname}) ||
		(!$strict && defined($obsoleteDevnames{$devname})));
}

sub readOneDevice {
	my ($devname)=@_;
	my ($filename,$configLine,$key,$plus,$value);

	foreach $filename ($etcPtalDefaults,"$etcPtal/$devname") {
		# We want the file-format version to come from the
		# device-specific file.
		delete $configInfo{"$devname#$cfgstrVersion"};
		if (!open(CONFIG,$filename)) {
			next;
		}
		while (($configLine=<CONFIG>)) {
			if ($configLine=~/^\s*([^\s#+=]+)\s*(\+*)=+\s*/) {
				$key="$devname#$1";
				$plus=$2;
				$'=~/\s*$/;
				$value=$`;
				if ($plus!~/\+/) {
					delete $configInfo{$key};
				}
				if (defined($configInfo{$key})) {
					$configInfo{$key}.=" ";
				}
				$configInfo{$key}.=$value;
			}
		}
		close(CONFIG);
		# Black-list device files with missing/incorrect versions.
		delete $devnames{$devname};
		delete $obsoleteDevnames{$devname};
		if ($configInfo{"$devname#$cfgstrVersion"} eq
		    $currentFileFormatVersion) {
			$devnames{$devname}=1;
		} else {
			$obsoleteDevnames{$devname}=1;
		}
	}
}

sub readDeviceInfo {
	my ($filename);

	# Read in each device's config file.
	foreach $filename (glob("$etcPtal/mlc:par:*"),glob("$etcPtal/mlc:usb:*"),glob("$etcPtal/hpjd:*")) {
		&readOneDevice(substr($filename,rindex($filename,"/")+1));
	}

	# Determine default device name (if any).
	undef $defaultDevice;
	if (open(DEFDEV,$etcPtalDefaultDevice)) {
		$_=<DEFDEV>;
		if (/\s*(mlc:par:\S+)/ ||
		    /\s*(mlc:usb:\S+)/ ||
		    /\s*(hpjd:\S+)/) {
			$defaultDevice=$1;
		}
		close DEFDEV;
	}
}

sub pruneDeviceIDField {
	my ($field)=@_;
	if (!defined($field)) {
		return undef;
	}
	if ($field=~/:+([^:;]*)/) {
		return $1;
	}
	$field=~/([^:;]*)/;
	return $1;
}

sub lookupDevidMatch {
	my ($devname,$field)=@_;

	if ($configInfo{"$devname#$cfgstrMlcdAppend"}=~
	     /-devidmatch\s+"($field:*[^"]*)"/) {
		return $1;
	}

	return undef;
}

sub lookupModel {
	my ($devname)=@_;
	return &lookupDevidMatch($devname,"MO*DE*L");
}

sub lookupSerialNumber {
	my ($devname)=@_;
	return &lookupDevidMatch($devname,"SE*R*N");
}

sub strtol {
	my ($s)=@_;

	if ($s!~/^0/) {
		return $s;
	}

	return oct($s);
}

sub lookupParBaseAddress {
	my ($devname)=@_;

	if ($devname=~/^mlc:par:/ &&
	    $configInfo{"$devname#$cfgstrMlcdAppend"}=~
	     /-base\s+(\S+)/) {
		return &strtol($1);
	}

	return undef;
}

sub printDeviceList {
	my ($devname);

	print "Currently defined device names ([*]=default):\n";
	if (!&getDeviceCount) {
		print "    (none)\n";
		return;
	}

	my $goodFileFound;
	foreach $devname (sort(keys(%devnames))) {
		if ($devname eq $defaultDevice) {
			print "[*]";
		} else {
			print "   ";
		}
		print " \"$devname\"\n";

		my $info=&lookupModel($devname);
		if (defined($info)) {
			print
"        Model is \"".&pruneDeviceIDField($info)."\".\n";
		}

		$info=&lookupSerialNumber($devname);
		if (defined($info)) {
			print
"        Serial number is \"".&pruneDeviceIDField($info)."\".\n";
		}

		$info=&lookupParBaseAddress($devname);
		if (defined($info)) {
			printf
"        Parallel-port base address is \"0x%3.3X\".\n",$info;
		}

		$goodFileFound++;
	}

	my $obsoleteFileFound;
	foreach $devname (sort(keys(%obsoleteDevnames))) {
		if (!$obsoleteFileFound) {
			if ($goodFileFound) {
				print "\n";
			}
			print
"    *** The following devices are using an obsolete configuration\n".
"    *** file format.  Use \"$fullname setup\"\n".
"    *** to delete and re-probe them:\n";
		}
		print
"    --> \"$devname\"\n";

		$obsoleteFileFound++;
	}
}

sub lookupDevname {
	my ($ptalPrefix,$mdlLong,$sernLong,$parBaseAddress)=@_;
	my ($devname);

    foreach $devname (sort(keys(%devnames))) {
	my $mdlTest=&lookupModel($devname);
	my $sernTest=&lookupSerialNumber($devname);
	my $parBaseAddressTest=&lookupParBaseAddress($devname);
# print "lookupDevname: pP=$ptalPrefix,$devname, mL=$mdlLong,$mdlTest, sL=$sernLong,$sernTest, pBA=$parBaseAddress,$parBaseAddressTest.\n";

	if ($devname=~/^$ptalPrefix/ &&
	    (!defined($mdlLong) || !defined($mdlTest) || $mdlTest eq $mdlLong) &&
	    (!defined($sernLong) || !defined($sernTest) || $sernTest eq $sernLong) &&
	    ($ptalPrefix ne "mlc:par:" ||
	     $parBaseAddressTest==$parBaseAddress)) {
		return $devname;
	}
    }

	return undef;
}

sub deleteDevice {
	my ($devname)=@_;
	my ($key);

	if (!unlink("$etcPtal/$devname")) {
		return undef;
	}

	delete $devnames{$devname};
	delete $obsoleteDevnames{$devname};
	foreach $key (keys(%configInfo)) {
		if ($key=~/^$devname#/) {
			delete $configInfo{$key};
		}
	}

	return 1;
}

sub cardAccessDetected {
	my ($devname)=@_;

	return !system("ptal-connect $devname -service HP-CARD-ACCESS -socket 17 -noretry </dev/null >/dev/null 2>/dev/null");
}

sub cardReaderDetected {
	my ($devname)=@_;
	my $oid="1.1.2.67";

	my $pmlResponse=`ptal-pml $devname get-collection $oid 2>/dev/null`;
	$pmlResponse=~/^\s*$oid\s+(\S+)/;
	if ($1 eq "failed") {
		if (&cardAccessDetected($devname)) {
			return 1;
		}

	} elsif ($'=~/^\s*(\d+)/ && $1&0x08000 &&
	    &cardAccessDetected($devname)) {
		return 1;
	}

	return undef;
}

sub probeDevice {
	my ($probeName,$ptalMlcdProbeCmdline,$ptalMlcdCommonCmdline)=@_;
	my ($devname,$ptalPrefix,$ptalSuffix);
	my ($mdlLong,$sernLong,$parportBase,$startTime);

	if ($probeName=~/^(mlc:par:)/ ||
	    $probeName=~/^(mlc:usb:)/ ||
	    $probeName=~/^(hpjd:)/) {
		$ptalPrefix=$1;
	} else {
		die;
	}
	if ($ptalPrefix eq "mlc:par:") {
		$ptalMlcdCommonCmdline=~/-base\s+([0-9A-FXa-fx]+)/ || die;
		$parportBase=hex($1);
	}

	# Try to start ptal-mlcd for locally-connected devices.
	if ($ptalPrefix=~/^mlc:/) {
		&stopDaemons;
		if ($ptalPrefix eq "mlc:par:") {
			# Prevent stuck <Enter> keys when probing parallel port:
			sleep(1);
		}

		my $mlcdCmdline="ptal-mlcd $probeName $ptalMlcdProbeCmdline $ptalMlcdCommonCmdline >/dev/null 2>/dev/null";
# print "\n$mlcdCmdline\n\n";
		if (system($mlcdCmdline)) {
			print
"\n".
"    *** ptal-mlcd failed to start!  Check syslog file for error messages.\n";
			return;
		}
	}

	# Query the model and serial number fields in the device ID string.
	$startTime=time;
	$mdlLong=`ptal-devid $probeName -long -mdl 2>/dev/null`;
	chomp $mdlLong;
	if ($mdlLong=~/\S/) {
		$sernLong=`ptal-devid $probeName -long -sern 2>/dev/null`;
		chomp $sernLong;

		print
"\n".
"    Found \"".&pruneDeviceIDField($mdlLong)."\"";
		if ($sernLong=~/\S/) {
			print
"\n".
"    with serial number \"".&pruneDeviceIDField($sernLong)."\"";
		}
		print ".\n";
	}

    if ($ptalPrefix eq "hpjd:") {
	# For hpjd, in case libsnmp isn't available, allow the user
	# to save this config even if the device ID string can't be read.
	if ($mdlLong!~/\S/ && !&askYN(
"\n".
"    *** Unable to read device ID string!\n".
"    *** Do you want to add this device anyway",0)) {
		return;
	}
	$devname=$probeName;

    } else {
	# Uncomment these lines to test communication failures:
	# $mdlLong=""; # sleep(10);

	# Make sure we got a non-empty model string.
	if ($mdlLong!~/\S/) {
		# Detect condition where ptal-mlcd could read device ID
		# string but nothing more.
		my ($mdlShort)=`ptal-devid $probeName -previous -short -mdl 2>/dev/null`;
		chomp $mdlShort;
		if ($mdlShort=~/\S/) {
			my $elapsedTime=time-$startTime;
			print
"\n".
"    *** Found \"$mdlShort\" but failed to communicate with it!\n".
"    *** Elapsed time for this attempt was $elapsedTime second(s).\n".
"    *** Check syslog file for ptal-mlcd error messages.\n".
"    *** See hpoj documentation for troubleshooting information.\n";

		} else {
			print
"\n".
"    No device found.\n";
		}
		return;
	}

	# Do nothing if the device is already set up.
	$devname=&lookupDevname($ptalPrefix,$mdlLong,$sernLong,$parportBase);
	if (defined($devname)) {
		print
"    This device is already set up as \"$devname\".\n";
		return;
	}

	# Suggest default device name suffix and allow user to override.
	$ptalSuffix=&pruneDeviceIDField($mdlLong);
	while (42) {
		$ptalSuffix=~/:*([^:]+)$/;
		$ptalSuffix=$1;
		$ptalSuffix=~s![\s/]+!_!g;
		$devname=$ptalPrefix.$ptalSuffix;

		print
"\n".
"    This device will be set up as \"$devname\".\n".
"    Press <Enter> alone to continue or <Ctrl-D> to skip this device, or\n".
"    enter a different desired name suffix (without the \"$ptalPrefix\" prefix)\n".
"    here ---> ";
		$_=&ask;

		if (!defined($_)) {
skipDevice:
			print
"\n".
"    Skipping this device.\n";
			return;

		} elsif (/\S/) {
			$ptalSuffix=$_;

		} elsif (!$devnames{$devname}) {
			last;

		} else {
			$_=&askYN("\n    Replace existing device \"$devname\"",1);
			if (!defined($_)) {
				goto skipDevice;
			}
			if ($_) {
				last;
			}
		}
	}
    }

	# Delete any old/conflicting devices.
	print
"\n".
"    Setting up as \"$devname\".\n";
	&deleteDevice($devname);
	if ($ptalPrefix eq "mlc:par:") {
		while (42) {
			my $oldDevname=&lookupDevname($ptalPrefix,undef,undef,
				$parportBase);
			if (!defined($oldDevname)) {
				last;
			}
			print
"    Deleting old device on this port \"$oldDevname\".\n";
			&deleteDevice($oldDevname);
		}
	}

	# Open output file.
	if (!open(CONFIG,">$etcPtal/$devname")) {
		print
"    *** Can't open \"$etcPtal/$devname\" ($!)!\n";
		return;
	}

	# Write file header.
	$_=`date 2>/dev/null`;
	chomp;
	print CONFIG
"# Added $_ by \"$fullname setup\".\n".
"\n".
"# The basic format for this file is \"key[+]=value\".\n".
"# If you say \"+=\" instead of \"=\", then the value is appended to any\n".
"# value already defined for this key, rather than replacing it.\n".
"\n".
"# Comments must start at the beginning of the line.  Otherwise, they may\n".
"# be interpreted as being part of the value.\n".
"\n".
"# If you have multiple devices and want to define options that apply to\n".
"# all of them, then put them in the file $etcPtalDefaults, which is read\n".
"# in before this file.\n".
"\n".
"# The format version of this file:\n".
"#   $fullname ignores device configuration\n".
"#   files with incorrect/missing versions.\n".
"$cfgstrVersion=$currentFileFormatVersion\n";

	# Write model string.
	if ($mdlLong!~/\S/) {
		print CONFIG
"\n".
"# \"$fullname setup\" couldn't read the model\n".
"# but added this device anyway:\n".
"# ";
	} else {
		print CONFIG
"\n".
"# The device model that was originally detected on this port:\n".
"#   If this ever changes, then you should re-run\n".
"#   \"$fullname setup\"\n".
"#   to delete and re-probe this device.\n";
		if ($ptalPrefix eq "mlc:par:") {
			print CONFIG
"#   Comment out if you don't care what model is really connected to this\n".
"#   parallel port.\n";
		}
	}
	print CONFIG
"$cfgstrMlcdAppend+=-devidmatch \"$mdlLong\"\n";

	# Write serial-number string.
	if ($sernLong!~/\S/) {
		print CONFIG
"\n".
"# The device's serial number is unknown.\n".
"# ";
	} else {
		print CONFIG
"\n".
"# The serial number of the device that was originally detected on this port:\n";
		if ($ptalPrefix=~/^mlc:/) {
			print CONFIG
"#   Comment out if you want to disable serial-number matching.\n";
		}
	}
	print CONFIG
"$cfgstrMlcdAppend+=-devidmatch \"$sernLong\"\n";

	# Write stuff for ptal-mlcd and ptal-printd.
	if ($ptalPrefix=~/^mlc:/) {
		print
"    Enabling ptal-mlcd and ptal-printd.\n";
		print CONFIG
"\n".
"# Standard options passed to ptal-mlcd:\n".
"$cfgstrMlcdAppend+=$ptalMlcdCommonCmdline\n";
		if ($ptalPrefix eq "mlc:usb:") {
			my $usbWildcard=join('" "',@usbWildcards);
			if (defined($usbWildcard)) {
				print CONFIG
"$cfgstrMlcdAppend+=-device \"$usbWildcard\"\n";
			}
		}
		print CONFIG
"\n".
"# ptal-mlcd's remote console can be useful for debugging, but may be a\n".
"# security/DoS risk otherwise.  In any case, it's accessible with the\n".
"# command \"ptal-connect mlc:<XXX>:<YYY> -service PTAL-MLCD-CONSOLE\".\n".
"# Uncomment the following line if you want to enable this feature for\n".
"# this device:\n".
"# $cfgstrMlcdAppend+=-remconsole\n".
"\n".
"# If you need to pass any other command-line options to ptal-mlcd, then\n".
"# add them to the following line and uncomment the line:\n".
"# $cfgstrMlcdAppend+=\n".
"\n".
"# By default ptal-printd is started for mlc: devices.  If you use CUPS,\n".
"# then you may not be able to use ptal-printd, and you can uncomment the\n".
"# following line to disable ptal-printd for this device:\n".
"# $cfgstrPrintdStart=0\n";

	} elsif ($ptalPrefix eq "hpjd:") {
		print CONFIG
"\n".
"# By default ptal-printd isn't started for hpjd: devices.\n".
"# If for some reason you want to start it for this device, then\n".
"# uncomment the following line:\n".
"# $cfgstrPrintdStart=1\n";
	}

	print CONFIG
"\n".
"# If you need to pass any additional command-line options to ptal-printd,\n".
"# then add them to the following line and uncomment the line:\n".
"# $cfgstrPrintdAppend+=\n";

	# Probe for and setup ptal-photod support.
	if (&cardReaderDetected($probeName)) {
		print
"    Enabling ptal-photod.\n";
		print CONFIG
"\n".
"# Uncomment the following line to enable ptal-photod for this device:\n".
"$cfgstrPhotodStart=1\n".
"\n".
"# If you have more than one photo-card-capable peripheral and you want to\n".
"# assign particular TCP port numbers and mtools drive letters to each one,\n".
"# then change the line below to use the \"-portoffset <n>\" option.\n".
"$cfgstrPhotodAppend+=-maxaltports 26\n";
	}

	# We're done.  Close output file and read it back into our database.
	close(CONFIG);
	&readOneDevice($devname);
}

sub doStop {
	if (!$pleaseBeQuiet) {
		print "\nStopping the HP OfficeJet Linux driver.\n";
	}
	# TODO: Run per-device stop commands.
	# TODO: if (-f $ptalStopConf) { system("/bin/sh $ptalStopConf"); }
	&stopDaemons;
	&cleanup;
	if (defined($varLockSubsys) && -f "$varLockSubsys/$basename") {
		unlink "$varLockSubsys/$basename";
	}
	if (defined($varLock) && -f "$varLock/$basename") {
		unlink "$varLock/$basename";
	}
}

# Returns ptal-mlcd command-line fragment based on parport info.
sub foundParport {
	my ($baselow,$basehigh,$devnode)=@_;
	my ($mlcdParport);

	$mlcdParport=sprintf("-base 0x%X",$baselow);
	if (!defined($basehigh)) {
		$mlcdParport.=" -porttype bpp";
	} else {
		$mlcdParport.=sprintf(" -basehigh 0x%X",$basehigh);
	}
	if ($devnode=~/\S/ && -e $devnode) {
		$mlcdParport.=" -device $devnode";
	}

	push(@foundParports,$mlcdParport);
	return $mlcdParport;
}

sub probeParallelPorts {
    undef @foundParports;
    if ($osPlatform=~/Linux/) {
      # Linux 2.4:
      if (open(FIND,"find /proc/sys/dev/parport -name base-addr -print 2>/dev/null |")) {
	my (@linux24ParportFiles)=sort(<FIND>);
	my ($file);

	close FIND;

	foreach $file (@linux24ParportFiles) {
	    if (open(BASEADDR,$file)) {
		my (@baseaddrFile)=<BASEADDR>;
		my ($portnum,$baselow,$basehigh);

		close BASEADDR;

		undef $portnum;
		undef $baselow;
		undef $basehigh;
		if ($file=~/^\/proc\/sys\/dev\/parport\/[^\d]*(\d+)/) {
			$portnum=$1;
		}
		if (join("",@baseaddrFile)!~/(\d+)/) {
			next;
		}
		$baselow=$1;
		if ($'=~/(\d+)/) {
			$basehigh=$1;
		}

		&foundParport($baselow,$basehigh,"/dev/lp$portnum");
	    }
	}
	if ($#foundParports>=$[) {
		return @foundParports;
	}
      }

      # Linux 2.2:
      if (open(FIND,"find /proc/parport -name hardware -print 2>/dev/null |")) {
	my (@linux22ParportFiles)=sort(<FIND>);
	my (@linuxIoports,$file);

	close FIND;
	if (open(IOPORTS,"/proc/ioports")) {
		@linuxIoports=<IOPORTS>;
		close IOPORTS;
	}

	foreach $file (@linux22ParportFiles) {
	    if (open(HARDWARE,$file)) {
		my (@hardwareFile)=<HARDWARE>;
		my ($i,$j);

		close HARDWARE;

	      for ($i=$[;$i<=$#hardwareFile;$i++) {
		my ($portnum,$baselow,$basehigh);

		undef $portnum;
		undef $baselow;
		undef $basehigh;
		if ($hardwareFile[$i]!~/^\s*base:\s*0x([0-9a-fA-F]+)/) {
			next;
		}
		$baselow=hex($1);
		if ($file=~/\/proc\/parport\/[^\d]*(\d+)/) {
			$portnum=$1;
		}
		for ($j=$[;$j<=$#linuxIoports;$j++) {
			if ($linuxIoports[$j]=~/([0-9a-fA-F]+)[^\:]*:+\s*parport(\d+)/) {
				if ($2==$portnum && hex($1)!=$baselow) {
					$basehigh=hex($1);
				}
			}
		}

		&foundParport($baselow,$basehigh,"/dev/lp$portnum");
	      }
	    }
	}
	if ($#foundParports>=$[) {
		return @foundParports;
	}
      }
    }

	return undef;
}

sub doSetupDelete {
    while (42) {
	my ($devname);

	&printSeparator;
	&printDeviceList;

	if (!&getDeviceCount) {
		last;
	}
	print
"\n".
"Press <Enter> alone to continue, or if you would like to delete\n".
"or reconfigure one of the above-listed devices, then enter the\n".
"device name to delete here --->  ";
	$devname=&ask;
	if ($devname eq "") {
		last;
	}
	if (!&deviceIsDefined($devname)) {
		print "\n*** Device name \"$devname\" is not defined!\n";
		next;
	}
	if (!&askYN("\nAre you sure you want to delete device \"$devname\"",0)) {
		print "\nCancelled deletion of device \"$devname\".\n";
	} elsif (!&deleteDevice($devname)) {
		print "\n*** Failed to delete device \"$devname\" ($!)!\n";
	} else {
		print "\nSuccessfully deleted device \"$devname\".\n";
	}
    }
}

sub ptalMlcdSupports {
	my ($bus)=@_;

	return !system("ptal-mlcd | grep \"<bus> is the connection type\" | grep \"$bus\" >/dev/null 2>/dev/null");
}

sub linuxModuleIsLoaded {
	my ($module)=@_;
	open(MODULES,"/proc/modules") || open(MODULES,"/sbin/lsmod |") ||
		return undef;
	while (<MODULES>) {
		if (/^\s*$module\b/) {
			close MODULES;
			return 1;
		}
	}
	close MODULES;
	return 0;
}

sub linuxInsmod {
	my ($module,$quiet)=@_;
	if (system("/sbin/modprobe $module >/dev/null 2>/dev/null") ||
	    !linuxModuleIsLoaded($module)) {
		if (!$quiet) {
			print
"\n".
"*** Warning: Couldn't load kernel module \"$module\"!\n";
		}
		return undef;
	}
	if (!$quiet) {
		print
"\n".
"Successfully loaded kernel module \"$module\" for the device probe.\n";
	}
	return 1;
}

sub linuxRmmod {
	my ($module)=@_;
	if (system("/sbin/rmmod $module >/dev/null 2>/dev/null")) {
		print
"\n".
"*** Warning: Couldn't unload kernel module \"$module\"!\n";
		return undef;
	}
	print
"\n".
"Successfully unloaded kernel module \"$module\".\n";
	return 1;
}

sub linuxModuleIsDisabledIn {
	my ($module,$filename,$regex)=@_;
	open(FILE,$filename) || return undef;
	while (<FILE>) {
		if (/^\s*$regex\b/) {
			close FILE;
			return 1;
		}
	}
	close FILE;
	return 0;
}

sub linuxModuleIsDisabledEtcModulesConf {
	my ($module)=@_;
	return linuxModuleIsDisabledIn($module,"/etc/modules.conf",
		"alias\\s+$module\\s+off");
}

sub linuxModuleIsDisabledEtcHotplugBlacklist {
	my ($module)=@_;
	return linuxModuleIsDisabledIn($module,"/etc/hotplug/blacklist",
		$module);
}

# Returns true iff module is disabled in /etc/modules.conf and/or in
# /etc/hotplug/blacklist, regardless of whether it's actually loaded:
sub linuxModuleIsDisabled {
	return (&linuxModuleIsDisabledEtcModulesConf(@_) ||
		&linuxModuleIsDisabledEtcHotplugBlacklist(@_));
}

sub linuxDisableModuleIn {
	my ($module,$filename,$line,$reason,$otherFilename)=@_;

	if (!open(FILE,">>$filename")) {
		print
"\n".
"*** Failed to update $filename to disable kernel module \"$module\"!\n";
		return undef;
	}

	$_=`date 2>/dev/null`;
	chomp;
	print FILE
"\n".
"# Added $_ by \"$fullname setup\"\n";
	if (defined($reason)) {
		print FILE "# $reason.\n";
	}
	if (defined($otherFilename)) {
		print FILE "# See also \"$otherFilename\".\n";
	}
	print FILE "$line\n\n";

	close FILE;
	print
"\n".
"Successfully updated $filename to disable kernel module \"$module\".\n";
	return 1;
}

sub linuxDisableModuleEtcModulesConf {
	my ($module,$reason)=@_;
	return linuxDisableModuleIn($module,
		"/etc/modules.conf","alias $module off",$reason,
		"/etc/hotplug/blacklist");
}

sub linuxDisableModuleEtcHotplugBlacklist {
	my ($module,$reason)=@_;
	return linuxDisableModuleIn($module,
		"/etc/hotplug/blacklist",$module,$reason,
		"/etc/modules.conf");
}

sub linuxDisableAndUnloadModule {
	my ($module,$reason)=@_;
	my $r1=1;
	my $r2=1;
	my $r3=1;

	if (!&linuxModuleIsDisabledEtcModulesConf($module)) {
		$r1=&linuxDisableModuleEtcModulesConf($module,$reason);
	}
	if (!&linuxModuleIsDisabledEtcHotplugBlacklist($module)) {
		$r2=&linuxDisableModuleEtcHotplugBlacklist($module,$reason);
	}
	if (&linuxModuleIsLoaded($module)) {
		$r3=&linuxRmmod($module);
	}

	return (($r1 || $r2) && $r3);
}

sub loadParModules {
	my ($quiet)=@_;
	if ($osPlatform=~/Linux/) {
		return &linuxInsmod("lp",$quiet);
	}
	return 1;
}

sub doSetupParallel {
	if (!&ptalMlcdSupports("par")) {
		print
"hpoj not compiled for parallel-port support; skipping parallel device probe.\n";
		return;

	} elsif (!&askYN("Probe for parallel-connected devices",1)) {
		return;
	}
	my $mlcdOptionsAll=$askYNSuffix;

	&loadParModules;
	&probeParallelPorts;

	print
"\n".
"Warning: Probing incorrect I/O port addresses could result in system\n".
"instability and/or data loss!  Consult your hardware documentation, BIOS\n".
"setup and/or kernel messages to verify correct base addresses in the\n".
"following prompts.   Also, take care not to probe parallel ports that\n".
"have non-printer devices (such as disk or tape drives) connected.\n";

	if ($#foundParports<$[) {
		print
"\n".
"No parallel ports were auto-detected.\n";
	}

    while (42) {
	if ($#foundParports<$[) {
		my ($baselow,$basehigh,$devnode);

		print
"\n".
"Press <Enter> alone to continue, or if you would like to probe an\n".
"undetected parallel port, then enter its hexadecimal base address\n".
"(such as \"378\", \"278\", or \"3BC\") here --->  ";
		$baselow=&ask;
		if ($baselow!~/[0xX]*([0-9a-fA-F]+)/) {
			last;
		}
		$baselow=hex($1);

		printf(
"\n".
"Press <Enter> alone to probe the default ECP-high base address\n".
"of 0x%X or <Ctrl-D> if this port has no ECP-high registers, or\n".
"enter a different ECP-high base address here --->  ",
			$baselow+0x400);
		$basehigh=&ask;
		if (!defined($basehigh)) {
			# Means non-ECP port (handled in &foundParport).
		} elsif ($basehigh!~/[0xX]*([0-9a-fA-F]+)/) {
			$basehigh=$baselow+0x400;
		} else {
			$basehigh=hex($1);
		}

		print
"\n".
"If this port is mapped to a kernel device node such as \"$suggestedParDevnode\",\n".
"then specify that information now so ptal-mlcd can perform proper device\n".
"locking.  Press <Enter> alone if this port is not mapped to a kernel\n".
"device, or enter the kernel device node filename here --->  ";
		$devnode=&ask;

		&foundParport($baselow,$basehigh,$devnode);
	}

	my ($foundParport)=shift(@foundParports);
	if (!defined($foundParport)) {
		last;
	}
	if (defined($mlcdOptionsAll)) {
		$foundParport.=" $mlcdOptionsAll";
	}

	if (&askYN("\nProbe parallel port \"$foundParport\"",0)) {
		if (defined($askYNSuffix)) {
			$foundParport.=" $askYNSuffix";
		}
		&probeDevice("mlc:par:probe",undef,$foundParport);
	}
    }
}

sub ptalMlcdSupportsLibusb {
	if (defined($forcelibusb)) { return $forcelibusb; }

	return !system("ptal-mlcd | grep libusb >/dev/null 2>/dev/null");
}

sub linuxIsSmp {
	if (defined($forcesmp)) { return $forcesmp; }

	open(CPUINFO,"/proc/cpuinfo") || return undef;
	while (<CPUINFO>) {
		if (/^\s*processor\s*:+\s*[1-9]/i) {
			close CPUINFO;
			return 1;
		}
	}
	close CPUINFO;
	return 0;
}

# Detects Linux kernel USB printer-class driver, whether statically linked or
# dynamically loaded:
sub linuxUsblpIsLoaded {
	open(DRIVERS,"/proc/bus/usb/drivers") || return undef;
	while (<DRIVERS>) {
		if (/usblp/) {
			close DRIVERS;
			return 1;
		}
	}
	close DRIVERS;
	return 0;
}

# Possible return values:
# undef -- skip USB probe
# 0 -- do nothing, proceed with USB probe
# >0 -- load printer.o
# <0 -- disable and unload printer.o/usblp.o
sub linuxWhatShouldWeDoAboutUsbPrinterModule {
	my $isSmp=&linuxIsSmp;
	my $libusbSupported=&ptalMlcdSupportsLibusb;
	my $usblpIsLoaded=&linuxUsblpIsLoaded;
	my $printerIsLoaded=&linuxModuleIsLoaded($usbprintermodule);

	# non-SMP:
	if (!$isSmp) {
		# No libusb support (not necessarily fatal if non-SMP):
		if (!$libusbSupported) {
allowInsmodPrinter:
			print
"\n".
"*** Note: hpoj appears not to have been compiled for libusb support.\n".
"*** This may or may not be a problem, depending on which model you have.\n".
"*** See http://hpoj.sourceforge.net/suplist.shtml for more information.\n";

			if (!$usblpIsLoaded) {
				return 1;
			}
		}

	# SMP, no libusb:
	} elsif (!$libusbSupported) {
		if (!&askYN(
"\n".
"*** Warning: Your system appears to be running in SMP (multi-processor)\n".
"*** mode.  hpoj appears not to have been compiled for libusb support.\n".
"*** Use of the kernel USB printer-class driver instead of libusb on SMP\n".
"*** systems may result in system instability or crashes with some\n".
"*** kernel versions.  You should install libusb and recompile hpoj.\n".
"\n".
"*** Are you sure you want to continue with the USB probe"
		     ,0)) {
			return undef;
		}
		goto allowInsmodPrinter;

	# SMP, libusb support, some sort of printer.c/usblp.c
	# functionality loaded:
	} elsif ($usblpIsLoaded) {
	    # printer.c/usblp.c compiled into the kernel:
	    if (!$printerIsLoaded) {
		if (!&askYN(
"\n".
"*** Warning: Your system appears to be running in SMP (multi-processor)\n".
"*** mode.  The kernel USB printer-class driver appears to be compiled\n".
"*** into the kernel.  Use of the kernel USB printer-class driver\n".
"*** instead of libusb on SMP systems may result in system instability\n".
"*** or crashes with some kernel versions.  You should recompile your\n".
"*** kernel with the USB printer-class driver either disabled entirely\n".
"*** or enabled as a module.\n".
"\n".
"*** Are you sure you want to continue with the USB probe"
		     ,0)) {
			return undef;
		}

	    # printer.c/usblp.c compiled and loaded as a module:
	    } else {
promptDisableRmmodPrinter:
		my $r=&askYN(
"\n".
"*** Warning: Your system appears to be running in SMP (multi-processor)\n".
"*** mode.  The kernel USB printer-class driver appears to be loaded\n".
"*** as a module and/or enabled to be auto-loaded.  Use of the kernel USB\n".
"*** printer-class driver instead of libusb on SMP systems may result in\n".
"*** system instability or crashes with some kernel versions.\n".
"\n".
"*** Would you like the kernel USB printer-class driver to be\n".
"*** disabled and unloaded now for the USB probe",1);
		if (!defined($r)) {
			return undef;
		}
		if ($r) {
			return (-1," (at your own risk)");
		}
	    }

	# SMP, libusb support, printer.c/usblp.c enabled although not 
	# loaded:
	} elsif (!&linuxModuleIsDisabled($usbprintermodule)) {
		goto promptDisableRmmodPrinter;
	}

	return 0;
}

# Returns nonzero iff it's OK to continue with the USB probe.
sub loadUsbModules {
	my ($quiet)=@_;

	if ($osPlatform=~/Linux/) {
		my ($r,$msg);
		if ($quiet) {
			goto linuxJustLoad;
		}
		($r,$msg)=&linuxWhatShouldWeDoAboutUsbPrinterModule;
		if (!defined($r)) {
			return undef;
		}
		if (!$r) {
			return 1;
		}
		if ($r<0) {
			$r=&linuxDisableAndUnloadModule($usbprintermodule,
"to prevent possible system instability due to SMP+USB");
		} else {
linuxJustLoad:
			$r=&linuxInsmod($usbprintermodule,$quiet);
		}
		if (!$r && !$quiet &&
		    &askYN("\n*** Continue with the USB probe anyway".$msg,0)) {
			$r=1;
		}
		return $r;
	}

	return 1;
}

# Returns nonzero iff it's OK to continue with the USB probe.
sub unloadUsbModules {
    if ($osPlatform=~/Linux/) {
	if (open(DEVICES,"/proc/bus/usb/devices")) {
		my ($line,$isHP,$hasPrinterIface,$scannerIsBound);
		while (42) {
			$line=<DEVICES>;
			if (!defined($line) || $line=~/^T:/) {
				if ($isHP && $hasPrinterIface &&
				    $scannerIsBound) {
					my $r=&askYN(
"\n".
"*** Warning: The Linux kernel USB scanner.o driver appears to have\n".
"*** improperly claimed an HP USB printer device.  It is necessary to\n".
"*** disable and unload scanner.o in order to increase the chance that\n".
"*** hpoj will be able to successfully detect and use this device.\n".
"*** (If you also have a USB single-function scanner, then you may\n".
"*** subsequently need to reconfigure it to use libusb.)\n".
"\n".
"*** Would you like the kernel USB scanner.o driver to be\n".
"*** disabled and unloaded now for the USB probe",1);
					if (!defined($r)) {
						close DEVICES;
						return undef;
					}
					if ($r) {
						&linuxDisableAndUnloadModule(
							"scanner",
"to prevent scanner.o from binding to hpoj devices");
					}
					last;
				}
				$isHP=0;
				$hasPrinterIface=0;
				$scannerIsBound=0;
			}
			if (!defined($line)) {
				last;
			}
			if ($line=~/^P:/) {
				if ($line=~/Vendor\s*=+[0\s]*3f0/i) {
					$isHP=1;
				}

			} elsif ($line=~/^I:/) {
				if ($line=~/Cls\s*=+[0\s]*7\s*\(\s*print/i) {
					$hasPrinterIface=1;
				}
				if ($line=~/Driver\s*=+\s*usbscanner/i) {
					$scannerIsBound=1;
				}
			}
		}
		close DEVICES;
	}
    }

	# TODO: For *BSD, may need to run "/sbin/kldunload ulpt".

	return 1;
}

sub doSetupUsb {
	if (!&ptalMlcdSupports("usb")) {
		print
"hpoj not compiled for USB support; skipping USB device probe.\n";
		return;

	} elsif (!&askYN("Probe for USB-connected devices",1)) {
		return;
	}
	my $mlcdOptionsAll=$askYNSuffix;
	if (!&loadUsbModules || !&unloadUsbModules) {
		return;
	}

	my $probeName="mlc:usb:probe";
	my $probeCmdline="ptal-mlcd $probeName -remconsole";
	my $devnode;
	my @devnodes;
	my $usbWildcard=join('" "',@usbWildcards);
	if (defined($usbWildcard)) {
		$probeCmdline.=" -device \"$usbWildcard\"";
		@devnodes=glob($usbWildcard);
	}

	&stopDaemons;
	if (!system("$probeCmdline >/dev/null 2>/dev/null")) {
		if (open(PTAL_CONNECT,"ptal-connect $probeName ".
		     "-service PTAL-MLCD-GLOB-DEVNODES 2>/dev/null |")) {
			while (<PTAL_CONNECT>) {
				chomp;
				my $i=$[;
				while ($_ ne $devnodes[$i]) {
					if ($i>=$#devnodes) {
						push(@devnodes,$_);
						last;
					}
					$i++;
				}
			}
			close PTAL_CONNECT;
		}
	}

	foreach $devnode (@devnodes) {
		print "\nProbing \"$devnode\"...  ";
		&probeDevice($probeName,"-device $devnode -noglobusb",
			$mlcdOptionsAll);
	}
}

sub doSetupJetDirect {
    while (42) {
	my ($hostname,$portnum,$devname);

	print
"Press <Enter> alone to continue, or if you would like to add a\n".
"JetDirect-connected device, then enter its dotted-decimal\n".
"IP address or hostname here --->  ";
	$hostname=&ask;
	if ($hostname eq "") {
		last;
	}
	$devname="hpjd:$hostname";

	# TODO: Detect and probe all ports of a multi-port JetDirect.
	# But still fall back to port-number prompt in case of no SNMP.
	print
"\n".
"If this is a multi-port JetDirect (500X), then enter the\n".
"port number ([1]/2/3) here --->  ";
	$portnum=&ask;
	if ($portnum=~/^\s*([123])\s*$/) {
		$portnum=$1;
		$devname.=":$portnum";
	}

	&probeDevice($devname);
	print "\n";
    }
}

sub doSetupDefault {
	if (&getDeviceCount<=1 && !defined($defaultDevice)) {
		return;
	}

tryAgain:
	&printSeparator;
	&printDeviceList;

	if (defined($defaultDevice)) {
		print
"\n".
"The system-wide default device name is currently set to\n".
"\"$defaultDevice\".\n".
"\n".
"Press <Enter> alone to keep this default or <Ctrl-D> to unset it,\n";
	} else {
		print
"\n".
"A system-wide default device name is not currently defined.\n".
"\n".
"Press <Enter> alone to continue with no default,\n";
	}
	print "or enter a new default device name here --->  ";
	my $devname=&ask;
	if (!defined($devname)) {
		if (!unlink($etcPtalDefaultDevice)) {
			if (defined($defaultDevice)) {
				print
"\n".
"*** Failed to remove default device name ($!)!\n";
			}
		} else {
			print
"\n".
"Successfully removed default device name.\n";
		}
		undef $defaultDevice;
	} elsif ($devname ne "") {
		# Don't allow undefined device names to be set as default.
		if (!&deviceIsDefined($devname,1)) {
			print
"\n".
"*** Device name \"$devname\" is not defined!\n";
			goto tryAgain;
		}
		if (!open(DEFDEV,">$etcPtalDefaultDevice")) {
			print
"\n".
"*** Failed to set default device name ($!)!\n";
		} else {
			$defaultDevice=$devname;
			print DEFDEV "$defaultDevice\n";
			close DEFDEV;
			print
"\n".
"Successfully set default device name to \"$defaultDevice\".\n";
		}
	}
}

sub doSetup {
	&printSeparator;
	print
"This program manages devices controlled by the HP OfficeJet Linux\n".
"driver (hpoj).  It attempts to probe your computer for local parallel-\n".
"and USB-connected devices, and allows you to specify network addresses\n".
"for remote JetDirect-connected devices.\n".
"\n".
"If you experience any difficulties in detecting your device(s), then\n".
"refer to the hpoj documentation for troubleshooting information.\n";

	# Offer to delete devices.
	&doSetupDelete;

	# Probe for parallel-connected devices.
	&printSeparator;
	&doSetupParallel;

	# Probe for USB-connected devices.
	&printSeparator;
	&doSetupUsb;

	# Offer to add JetDirect-connected devices.
	&printSeparator;
	&doSetupJetDirect;

	# Re-list devices and ask user which one should be the default.
	&doSetupDefault;

	&printSeparator;
	print
"Done updating device configuration files stored under $etcPtal.\n".
"If you make manual changes to those files, then be sure to run\n".
"\"$fullname start\" so they will take effect.\n";
	&printSeparator;

	&stopDaemons;
	&cleanup;
}

sub doStart {
	my ($devname);
	my (@sortedDevnames)=sort(keys(%devnames));

	if (!$pleaseBeQuiet) {
		print "Starting the HP OfficeJet Linux driver.\n";
	}
    if ($#sortedDevnames<$[) {
	if (!$pleaseBeQuiet) {
		print "    No hpoj devices have been configured.\n";
		print "    As root, run \"$fullname setup\".\n";
	}

    } else {
	my $parModulesLoaded=0;
	# Don't "modprobe printer/usblp" on SMP Linux with libusb support:
	my $usbModulesLoaded=
		(&ptalMlcdSupportsLibusb &&
		 $osPlatform=~/Linux/ && &linuxIsSmp);
	my $ptalPhotodLoaded=0;

      foreach $devname (@sortedDevnames) {
	if (!$pleaseBeQuiet) {
		if (1 && $devname eq $defaultDevice) {
			print "[*]";
		} else {
			print "   ";
		}
		print " $devname\n";
	}

	# Try to load kernel modules if necessary.
	if ($devname=~/^mlc:par:/ && !$parModulesLoaded) {
		&loadParModules(1);
		$parModulesLoaded=1;
	} elsif ($devname=~/^mlc:usb:/ && !$usbModulesLoaded) {
		&loadUsbModules(1);
		$usbModulesLoaded=1;
	}

	# Start ptal-mlcd if necessary.
	my $ptalMlcdCmdline;
	if ($devname=~/^mlc:/) {
		$ptalMlcdCmdline="ptal-mlcd $devname ".
			$configInfo{"$devname#$cfgstrMlcdAppend"};

	} elsif ($devname=~/^hpjd:/) {
		# Don't start ptal-mlcd for this case.
	}
	if (defined($ptalMlcdCmdline)) {
		if (!$pleaseBeQuiet && $pleaseBeVerbose) {
			print "        $ptalMlcdCmdline\n";
		}
		if (system($ptalMlcdCmdline)) {
			$retcode=1;
		}
	}

	# Start ptal-printd if necessary
	# (by default, for mlc: but not for hpjd:).
	my $startPtalPrintd=$configInfo{"$devname#$cfgstrPrintdStart"};
	if (!defined($startPtalPrintd)) {
		if ($devname=~/^mlc:/) {
			$startPtalPrintd=1;
		} elsif ($devname=~/^hpjd:/) {
			$startPtalPrintd=0;
		}
	}
	if ($startPtalPrintd) {
		my $ptalPrintdCmdline="ptal-printd $devname -morepipes 9";
		if (defined($ptalPrintdLike)) {
			$ptalPrintdCmdline.=" -like $ptalPrintdLike";
		}
		$ptalPrintdCmdline.=" ".
			$configInfo{"$devname#$cfgstrPrintdAppend"};

		if (!$pleaseBeQuiet && $pleaseBeVerbose) {
			print "        $ptalPrintdCmdline\n";
		}
		if (system($ptalPrintdCmdline)) {
			$retcode=1;
		}
	}

	# Start ptal-photod if necessary.
	if ($configInfo{"$devname#$cfgstrPhotodStart"}) {
		if (!$ptalPhotodLoaded) {
			sleep(2);
			$ptalPhotodLoaded=1;
		}
		my $ptalPhotodCmdline="ptal-photod $devname ".
			$configInfo{"$devname#$cfgstrPhotodAppend"};
		if (!$pleaseBeQuiet && $pleaseBeVerbose) {
			print "        $ptalPhotodCmdline\n";
		}
		if (system($ptalPhotodCmdline)) {
			$retcode=1;
		}
	}
      }

	# TODO: -f $ptalStartConf && system("/bin/sh $ptalStartConf");
	# TODO: Run per-device start commands.
	if (defined($varLockSubsys) && -d $varLockSubsys) {
		system("touch $varLockSubsys/$basename >/dev/null 2>/dev/null");
	} elsif (defined($varLock) && -d $varLock) {
		system("touch $varLock/$basename >/dev/null 2>/dev/null");
	}
    }
	if (!$pleaseBeQuiet) {
		print "\n";
	}
}

# Additional command-line switches with which we won't clutter up the
# help message (because they're mainly for testing/debugging purposes):
#	-forcelibusb, -hidelibusb -- Forces or hides libusb detection.
#	-forcesmp, -hidesmp -- Forces or hides SMP detection.
sub syntaxError {
	print "\n";
	&printDeviceList;

	die
"\n".
"Syntax for the root user:\n".
"    $basename start|stop|setup|status|condrestart [-q[uiet]|-v[erbose]]\n".
"\n";
}

sub main {
	&setupVariables;
	&readDeviceInfo;

	# Handle "status" option separately so it works for all users.
	my $arg=shift(@ARGV);
	my $running=
		((defined($varLockSubsys) && -f "$varLockSubsys/$basename") ||
		 (defined($varLock) && -f "$varLock/$basename"));
	if ($arg eq "status") {
		if ($running) {
			print "$basename has been started.\n";
			return 0;
		}
		print "$basename is stopped.\n";
		return 1;
	}

	# Should be run as root.
	# For non-root users we will be nice and print the device list at least.
	if ($> && $<) {
		&syntaxError;
	}

	# Parse command line and figure out what we're supposed to do.
	my ($pleaseStart,$pleaseSetup);
	while ($#ARGV>=$[) {
		$_=shift(@ARGV);
		if (/^-+q/) {
			$pleaseBeQuiet=1;
			$pleaseBeVerbose=0;
		} elsif (/^-+v/) {
			$pleaseBeQuiet=0;
			$pleaseBeVerbose=1;
		} elsif (/^-+forcelibusb/) {
			$forcelibusb=1;
		} elsif (/^-+hidelibusb/) {
			$forcelibusb=0;
		} elsif (/^-+forcesmp/) {
			$forcesmp=1;
		} elsif (/^-+hidesmp/) {
			$forcesmp=0;
		} else {
			&syntaxError;
		}
	}
	if ($arg eq "start" || $arg eq "restart" || $arg eq "reload" || $arg eq "force-reload") {
		$pleaseStart=1;
	} elsif ($arg eq "condrestart") {
		if (!$running) {
			return 0;
		}
		$pleaseStart=1;
	} elsif ($arg eq "stop") {
		# Nothing extra here.
	} elsif ($arg eq "setup" || $arg eq "probe") {
		$pleaseSetup=1;
		$pleaseStart=1;
		$pleaseBeQuiet=0;
	} else {
		&syntaxError;
	}

	# We will always at least stop the daemons given a valid command.
	&doStop;

	# Probe for new devices if so requested.
	if ($pleaseSetup) {
		&doSetup;
	}

	# Start the daemons if so requested.
	if ($pleaseStart) {
		&doStart;
	}

	return $retcode;
}

exit(&main);
