#!/usr/bin/perl
# LANG=C is a work around for bug 82652.
# The easier work-around of setting LANG=C in perl script itself didn't
# work.  It's probably too late by then.
eval '(exit $?0)' && eval 'exec env LANG=C $0 ${1+"$@"}' 
  if $ENV{"LANG"} != "C";
# 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).

# Copyright (C) 2001-2002 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]*",
	"/dev/ulpt[0-9]*"
	);

# If necessary, edit the following lines to specify (within the double quotes)
# additional options that need to be passed to ptal-mlcd to fix or debug the
# device probe (for example, "-log" turns on full debug messages):
my $parProbeAdditionalOptions="";
my $usbProbeAdditionalOptions="";

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


# 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 $varLock="/var/lock";
my $varLockSubsys="$varLock/subsys";
my $osPlatform=`uname -s`;
my %devnames;
my %obsoleteDevnames;
my %configInfo;
my $defaultDevice;
my $usbWildcard;
my $parWildcard;
my $ptalPrintdLike;
my $suggestedParDevnode="/dev/lp0";
my $pleaseBeQuiet;
my $pleaseBeVerbose;
my @foundParports;
my $currentFileFormatVersion=1;


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/sbin:$prefix/bin:/sbin:/usr/sbin:".$ENV{"PATH"};
	$ENV{"LD_LIBRARY_PATH"}="$prefix/lib:".$ENV{"LD_LIBRARY_PATH"};
	$parWildcard=&pickWildcardPattern(@parWildcards);
	$usbWildcard=&pickWildcardPattern(@usbWildcards);
	if (defined($parWildcard)) {
		($ptalPrintdLike)=<${parWildcard}>;
		$suggestedParDevnode=$ptalPrintdLike;
	} elsif (defined($usbWildcard)) {
		($ptalPrintdLike)=<${usbWildcard}>;
	}
}

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";
}

sub loadParModules {
	if ($osPlatform=~/Linux/) {
		system("/sbin/modprobe lp >/dev/null 2>/dev/null");
	}
}

sub loadUsbModules {
	if ($osPlatform=~/Linux/) {
		system("/sbin/modprobe printer >/dev/null 2>/dev/null");
	}
}

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);

	print "$question (".($default?"[y]/n":"y/[n]").")?  ";
	$response=&ask;
	if (!defined($response)) {
		return undef;
	}
	if ($response=~/^\s*[Yy]/) {
		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 (system("ptal-mlcd $probeName $ptalMlcdProbeCmdline $ptalMlcdCommonCmdline >/dev/null 2>/dev/null")) {
			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`;
	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+=";
		if ($ptalPrefix eq "mlc:usb:") {
			# Important: don't put more quotes around $usbWildcard,
			# because ptal-mlcd currently does no globbing:
			print CONFIG "-device $usbWildcard";
			if ($ptalMlcdCommonCmdline=~/\S/) {
				print CONFIG " ";
			}
		}
		if ($ptalMlcdCommonCmdline=~/\S/) {
			print CONFIG "$ptalMlcdCommonCmdline";
		}
		print CONFIG "\n".
"\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";
	}
}

# Construct ptal-mlcd switches 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/) {
		$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 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;
	}

	&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 removable 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 (&askYN("\nProbe parallel port \"$foundParport\"",0)) {
		if ($parProbeAdditionalOptions=~/\S/) {
			$foundParport.=" $parProbeAdditionalOptions";
		}
		&probeDevice("mlc:par:probe",undef,$foundParport);
	}
    }
}

sub doSetupUsb {
	my ($devnode);

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

	} elsif (!defined($usbWildcard)) {
		print
"No USB device nodes found on your system; skipping USB device probe.\n".
"Check the definition of \@usbWildcards near the top of $0.\n";
		return;

	} elsif (!&askYN("Probe for USB-connected devices",1)) {
		return;
	}
	&loadUsbModules;

	foreach $devnode (glob($usbWildcard)) {
		print "\nProbing \"$devnode\"...  ";
		&probeDevice("mlc:usb:probe","-device $devnode",
			$usbProbeAdditionalOptions);
	}
}

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 {
	my ($devname);

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 --->  ";
	$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 {
      foreach $devname (@sortedDevnames) {
	if (!$pleaseBeQuiet) {
		if (1 && $devname eq $defaultDevice) {
			print "[*]";
		} else {
			print "   ";
		}
		print " $devname\n";
	}

	# 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"}) {
		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";
	}
}

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=$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);
	$pleaseBeQuiet=1;
	if ($ARGV[$[+1]=~/^-+q/) {
		$pleaseBeQuiet=1;
	} elsif ($ARGV[$[+1]=~/^-+v/) {
		$pleaseBeVerbose=1;
	}
	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);
