cups-genppdupdate.in [plain text]
#! @PERL@ -w
# $Id: cups-genppdupdate.in,v 1.2 2004/07/30 01:28:56 jlovell Exp $
# Update CUPS PPDs for Gimp-Print queues.
# Copyright (C) 2002-2003 Roger Leigh (rleigh@debian.org)
#
# 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.
use strict;
use Getopt::Std;
use Fcntl qw(:mode);
use File::Temp qw(:POSIX);
use File::Copy qw(mv);
sub parse_options ();
sub update_ppd ($); # Original PPD filename
sub find_ppd ($$$$); # Gimp-Print Filename, driver, language (e.g. en, sv),
# region (e.g. GB, DE)
sub get_default_types (*); # Source PPD FH
sub get_defaults (*); # Source PPD FH
sub get_options (*\%); # Source PPD FH, default_types hash ref
our $opt_d; # Debug mode
our $opt_h; # Help
our $opt_n; # No action
our $opt_q; # Quiet mode
our $opt_s; # Source PPD location
our $opt_v; # Verbose mode
my $debug = 0;
my $verbose = 0; # Verbose output
if ($debug) {
$verbose = 1;
}
my $quiet = 0; # No output
my $no_action = 0; # Don't output files
my $version = "@GIMPPRINT_MAJOR_VERSION@.@GIMPPRINT_MINOR_VERSION@";
my $ppd_dir = "@cups_conf_serverroot@/ppd"; # Location of in-use CUPS PPDs
my $ppd_root_dir = "@cups_conf_datadir@/model";
my $ppd_base_dir = "$ppd_root_dir/gimp-print/$version"; # Available PPDs
my $gzext = ".gz";
my $updated_ppd_count = 0;
my @ppd_files; # A list of in-use Gimp-Print PPD files
# Used to convert a language name to its two letter code
my %languagemappings = (
"chinese" => "cn",
"danish" => "da",
"dutch" => "nl",
"english" => "en",
"finnish" => "fi",
"french" => "fr",
"german" => "de",
"greek" => "el",
"italian" => "it",
"japanese" => "jp",
"norwegian" => "no",
"polish" => "pl",
"portuguese" => "pt",
"russian" => "ru",
"slovak" => "sk",
"spanish" => "es",
"swedish" => "sv",
"turkish" => "tr"
);
# Check command-line options...
parse_options();
# Set a secure umask...
umask 0177;
# Find all in-use Gimp-Print PPD files...
my @ppdglob = glob("$ppd_dir/*.{ppd,PPD}");
my $ppdlist = join ' ', @ppdglob;
if (@ppdglob) {
open PPDFILES, "grep -i -l Gimp-Print $ppdlist|" or die "can't grep $ppd_dir/*: $!";
while (<PPDFILES>) {
chomp;
push @ppd_files, $_;
}
close PPDFILES or ($! == 0) or die "can't close grep pipe: $!";
}
# Exit if there are not files to update...
if (!@ppd_files) {
print STDOUT "No Gimp-Print PPD files to update.\n";
exit (0);
}
# Update each of the Gimp-Print PPDs, where possible...
foreach (@ppd_files) {
$updated_ppd_count += update_ppd($_);
}
if (!$quiet || $verbose) {
if ($updated_ppd_count > 0) {
print STDOUT "Updated $updated_ppd_count PPD files. Restart cupsd for the changes to take effect.\n";
exit (0);
} else {
print STDOUT "Failed to update any PPD files\n";
exit (0);
}
}
sub parse_options () {
getopts("dhnqs:v");
if ($opt_n) {
$no_action = 1;
}
if ($opt_d) {
$debug = 1;
}
if ($opt_s) {
if (-d $opt_s) {
$ppd_base_dir = "$opt_s";
}
else {
die "$opt_s: invalid directory: $!";
}
}
if ($opt_v) {
$verbose = 1;
$quiet = 0;
}
if ($opt_q) {
$verbose = 0;
$quiet = 1;
}
if ($opt_h) {
print "Usage: $0 [OPTION]...\n";
print "Update Gimp-Print PPD files.\n\n";
print " -d Enable debugging\n";
print " -h Display this help text\n";
print " -n No-action. Don't overwrite any PPD files.\n";
print " -q Quiet mode. No messages except errors.\n";
print " -s ppd_dir Use ppd_dir as the source PPD directory.\n";
print " -v Verbose messages.\n";
exit (0);
}
}
# Update the named PPD file.
sub update_ppd ($) {
my $ppd_source_filename = $_;
open ORIG, $_ or die "$_: can't open PPD file: $!";
seek (ORIG, 0, 0) or die "can't seek to start of PPD file";
if ($debug) {
print "Source Filename: $ppd_source_filename\n";
}
# Get the `PCFileName'; the new source PPD will have the same name.
my ($filename) = "";
my ($driver) = "";
my ($gimpprintdriver) = "";
my ($locale) = "";
my ($lingo) = "";
my ($region) = "";
my ($valid) = 0;
while (<ORIG>) {
if (/\*StpLocale:/) {
($locale) = m/^\*StpLocale:\s\"*(.*)\"$/;
$valid = 1;
}
if (/\*LanguageVersion/) {
($lingo) = m/^\*LanguageVersion:\s*(.*)$/;
}
if (/^\*StpDriverName:/ ) {
($driver) = m/^\*StpDriverName:\s*\"(.*)\"$/;
$valid = 1;
}
if (/\*%End of / && $driver eq "") {
($driver) = m/^\*%End of\s*(.*).ppd$/;
}
if (/^\*StpPPDLocation:/ ) {
($filename) = m/^\*StpPPDLocation:\s*\"(.*)\"$/;
$valid = 1;
}
if (/^\*%Gimp-Print Filename:/) {
$valid = 1;
}
}
if (! $valid) {
print STDERR "$ppd_source_filename: this PPD file cannot be upgraded automatically (only files based on Gimp-Print 4.3.21 and newer can be)\n";
return 0;
}
if ($debug) {
print "Gimp-Print Filename: $filename\n";
print "Locale: $locale\n";
print "Language: $lingo\n";
print "Driver: $driver\n";
}
if ($locale) {
# Split into the language and territory.
($locale, $region) = split(/-/, $locale);
} else {
# Split into the language and territory.
($locale, $region) = split(/-/, $lingo);
# Convert language into language code.
$locale = $languagemappings{"\L$lingo"};
if (!defined($locale)) {
$locale = "C"; # Fallback if there isn't one.
}
}
if (! defined($region)) {
$region = "";
}
if ($debug) {
print "Locale: $locale\n";
print "Region: $region\n";
}
# Search for a PPD matching our criteria...
my $source = find_ppd($filename, $driver, $locale, $region);
if (!defined($source)) {
# There wasn't a valid source PPD file, so give up.
print STDERR "$ppd_source_filename: no valid candidate for replacement. Skipping\n";
print STDERR "$ppd_source_filename: please upgrade this PPD manually\n";
return 0;
}
if ($debug) {
print "Candidate PPD: $source\n";
}
# Read in the new PPD, decompressing it if needed...
my $source_data;
my $suffix = "\\" . $gzext; # Add '\', so m// matches the '.'.
if ($source =~ m/.gz$/) { # Decompress input buffer
open GZIN, "gunzip -c $source |"
or die "$_: can't open for decompression: $!";
while (<GZIN>) {
$source_data .= $_;
}
close GZIN;
}
else {
open SOURCE, $source
or die "$source: can't open source file: $!";
binmode SOURCE;
my $source_size = (stat(SOURCE))[7];
read (SOURCE, $source_data, $source_size)
or die "$source: error reading source: $!";
close SOURCE or die "$source: can't close file: $!";
}
# Save new PPD in a temporary file, for processing...
my($tmpfile, $tmpfilename) = tmpnam();
chown(0, 0, $tmpfilename); # root.root
chmod(0644, $tmpfilename);
unlink $tmpfilename or warn "can't unlink temporary file $tmpfile: $!\n";
print $tmpfile $source_data;
# Extract the default values from the original PPD...
my %orig_default_types = get_default_types(ORIG);
my %new_default_types = get_default_types($tmpfile);
my %defaults = get_defaults(ORIG);
my %options = get_options($tmpfile, %new_default_types);
# Close original and temporary files...
close ORIG or die "$_: can't close file: $!";
close $tmpfile or die "can't close temporary file $tmpfile: $!";
if ($debug) {
print "Original Default Types:\n";
foreach (sort keys %orig_default_types) {
print " $_: $orig_default_types{$_}\n";
}
print "New Default Types:\n";
foreach (sort keys %new_default_types) {
print " $_: $new_default_types{$_}\n";
}
print "Defaults:\n";
foreach (sort keys %defaults) {
print " $_: $defaults{$_}\n";
}
print "Options:\n";
foreach (sort keys %options) {
print " $_: ";
foreach my $opt (@{$options{$_}}) {
print "$opt ";
}
print "\n";
}
}
# Update source buffer with old defaults...
# Loop through each default in turn.
default_loop:
foreach (sort keys %defaults) {
my $default_option = $_;
my $option;
($option = $_) =~ s/Default//; # Strip off `Default'
# Check method is valid
my $orig_method = $orig_default_types{$option};
my $new_method = $new_default_types{$option};
if ((!defined($orig_method) || !defined($new_method)) ||
$orig_method ne $new_method) {
next;
}
if ($new_method eq "PickOne") {
# Check the old setting is valid
foreach (@{$options{$option}}) {
if ($defaults{$default_option} eq $_) { # Valid option
# Set the option in the new PPD
$source_data =~ s/\*($default_option).*/*$1:$defaults{$default_option}/m;
if ($verbose) {
print "$ppd_source_filename: Set *$default_option to $defaults{$default_option}\n";
}
next default_loop;
}
}
printf STDERR
"$ppd_source_filename: Invalid option: *$default_option: $defaults{$default_option}. Skipped.\n";
next;
}
print STDERR
"$ppd_source_filename: PPD OpenUI method $new_default_types{$_} not understood. Skipped\n";
}
# Write new PPD...
my $tmpnew = "${ppd_source_filename}.new";
if (! open NEWPPD, "> $tmpnew") {
warn "Can't open $tmpnew for writing: $!\n";
return 0;
}
chown(0, 0, $tmpnew); # Bad idea to hardcode this...
chmod(0644, $tmpnew); # Bad idea to hardcode this...
print NEWPPD $source_data;
if (! close NEWPPD) {
warn "Can't close ${tmpnew}.new for writing: $!\n";
unlink $tmpnew;
return 0;
}
if (! rename $tmpnew, $ppd_source_filename) {
warn "Can't rename $tmpnew to $ppd_source_filename: $!\n";
unlink $tmpnew;
return 0;
}
if (!$quiet || $verbose) {
print STDOUT "Updated $ppd_source_filename using $source\n";
}
return 1;
# All done!
}
# Find a suitable source PPD file
sub find_ppd ($$$$) {
my($gimpprintfilename, $drivername, $lang, $region) = @_;
my $file; # filename to return
my ($key) = '^\\*FileVersion:[ ]*"@VERSION@"$';
my ($lingo, $suffix, $base, $basedir);
my ($current_best_file, $current_best_time);
my ($stored_name, $stored_dir);
$stored_name = $gimpprintfilename;
$stored_name =~ s,.*/([^/]*)(.gz)?$,$1,;
$stored_dir = $gimpprintfilename;
$stored_dir =~ s,(.*)/([^/]*)$,$1,;
$current_best_file = "";
$current_best_time = 0;
# All possible candidates, in order of usefulness and gzippedness
foreach $lingo ("${lang}_${region}/",
"$lang/",
"en/",
"C/",
"") {
foreach $suffix (".ppd$gzext",
".ppd") {
foreach $base ("${drivername}.$version",
"stp-${drivername}.$version",
$stored_name,
$drivername) {
foreach $basedir ($ppd_base_dir,
$stored_dir,
$ppd_root_dir) {
if (! $basedir || ! $base) { next; }
my ($fn) = "$basedir/$lingo$base$suffix";
if ($debug) {
print "Trying $fn for $gimpprintfilename, $lang, $region\n";
}
# Check that it is a regular file, owned by root.root, not writable
# by other, and is readable by root. i.e. the file is secure.
my @sb = stat $fn or next;
if (S_ISREG($sb[2]) && ($sb[4] == 0) && ($sb[5] == 0)) {
# !(S_IWOTH & $sb[2]) && (S_IRUSR & $sb[2])) {
# Check that the file is a valid Gimp-Print PPD file
# of the correct version.
my $file_version;
if ($fn =~ m/\.gz$/) {
$file_version = `gunzip -c $fn | grep '$key'`;
} else {
$file_version = `cat $fn | grep '$key'`;
}
if ($file_version ne "") {
if ($debug) {
print " Format valid: time $sb[9] best $current_best_time prev $current_best_file cur $fn!\n";
}
if ($sb[9] > $current_best_time) {
$current_best_time = $sb[9];
$current_best_file = $fn;
}
} elsif ($debug) {
print " Format invalid\n";
}
}
else {
if (! -d $fn && ! /\/$/) {
print STDERR "$fn: not a regular file, or insecure ownership and permissions. Skipped\n";
}
}
}
}
}
}
if ($current_best_file) {
return $current_best_file;
}
# Yikes! Cannot find a valid PPD file!
return undef;
}
# Return the default options from the given PPD filename
sub get_default_types(*) {
my $fh = $_[0];
my %default_types;
# Read each line of the original PPD file, and store all OpenUI
# names and their types in a hash...
seek ($fh, 0, 0) or die "can't seek to start of PPD file";
while (<$fh>) {
if ( m/^\*OpenUI/ ) {
chomp;
my ($key, $value) = /^\*OpenUI\s\*([[:alnum:]]+).*:\s([[:alnum:]]+)/;
if ($key && $value) {
$default_types{$key}=$value;
}
}
}
return %default_types;
}
# Return the default options from the given PPD filename
sub get_defaults(*) {
my $fh = $_[0];
my %defaults;
# Read each line of the original PPD file, and store all default
# names and their values in a hash...
seek ($fh, 0, 0) or die "can't seek to start of PPD file";
while (<$fh>) {
if ( m/^\*Default/ ) {
chomp;
my($key, $value) = /^\*([[:alnum:]]+):\s*([[:alnum:]]+)/;
if ($key && $value) {
$defaults{$key}=$value;
}
}
}
return %defaults;
}
# Return the available options from the given PPD filename
sub get_options(*\%) {
my $fh = $_[0];
my $validopts = $_[1];
my %options;
# For each valid option name, grab each valid option for that name
# and store in a hash of arrays...
foreach (sort keys %$validopts) {
my $tmp = $_;
my @optionlist;
seek ($fh, 0, 0) or die "can't seek to start of PPD file";
while (<$fh>) {
if ( m/^\*$tmp/ ) {
chomp;
my ($value) = /^\*$tmp\s*([[:alnum:]]+)[\/:]/;
if ($value) {
push @optionlist, $value;
}
}
}
if (@optionlist) {
$options{$tmp} = [ @optionlist ];
}
}
return %options;
}