You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1107 lines
35 KiB
1107 lines
35 KiB
2 months ago
|
#!/usr/bin/python3
|
||
|
# $Id$
|
||
|
# Update CUPS PPDs for Gutenprint queues.
|
||
|
# Copyright (C) 2002-2003 Roger Leigh (rleigh@debian.org)
|
||
|
# Copyright (C) 2009, 2011, 2014 Red Hat, Inc.
|
||
|
#
|
||
|
# 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.
|
||
|
|
||
|
import getopt
|
||
|
import glob
|
||
|
import io
|
||
|
import os
|
||
|
import re
|
||
|
import stat
|
||
|
import subprocess
|
||
|
import sys
|
||
|
from functools import reduce
|
||
|
|
||
|
global optargs
|
||
|
global debug
|
||
|
global verbose
|
||
|
global interactive
|
||
|
global quiet
|
||
|
global no_action
|
||
|
global reset_defaults
|
||
|
global version
|
||
|
global micro_version
|
||
|
global use_static_ppd
|
||
|
global file_version
|
||
|
|
||
|
global ppd_dir
|
||
|
global ppd_root_dir
|
||
|
global ppd_base_dir
|
||
|
global ppd_out_dir
|
||
|
global gzext
|
||
|
global updated_ppd_count
|
||
|
global skipped_ppd_count
|
||
|
global failed_ppd_count
|
||
|
global exit_after_parse_args
|
||
|
global languages
|
||
|
|
||
|
global serverdir
|
||
|
global driver_bin
|
||
|
global driver_version
|
||
|
global server_multicat
|
||
|
global server_multicat_initialized
|
||
|
|
||
|
global ppd_files
|
||
|
global languagemappings
|
||
|
|
||
|
def help():
|
||
|
print ("""
|
||
|
Usage: %s [OPTION]... [PPD_FILE]...
|
||
|
Update CUPS+Gutenprint PPD files.
|
||
|
|
||
|
-d flags Enable debugging
|
||
|
-h Display this help text
|
||
|
-n No-action. Don't overwrite any PPD files.
|
||
|
-q Quiet mode. No messages except errors.
|
||
|
-s ppd_dir Use ppd_dir as the source PPD directory.
|
||
|
-p ppd_dir Update PPD files in ppd_dir.
|
||
|
-P driver Use the specified driver binary to generate PPD files.
|
||
|
-v Verbose messages.
|
||
|
-N Reset options to defaults.
|
||
|
-o out_dir Output PPD files to out_dir.
|
||
|
-r version Use PPD files for Gutenprint major.minor version.
|
||
|
-f Ignore new PPD file safety checks.
|
||
|
-i Prompt (interactively) for each PPD file.
|
||
|
-l language Language choice (Gutenprint 5.1 or below).
|
||
|
Choices: %s
|
||
|
Or -loriginal to preserve original language
|
||
|
with Gutenprint 5.2 or above
|
||
|
""" % (sys.argv[0],
|
||
|
reduce (lambda x,y: "%s %s" % (x,y), languages)))
|
||
|
sys.exit (0)
|
||
|
|
||
|
def die_if_not_directory (dir):
|
||
|
try:
|
||
|
st = os.stat (dir)
|
||
|
if not stat.S_ISDIR (st.st_mode):
|
||
|
os.chdir (dir)
|
||
|
except OSError as err:
|
||
|
(e, s) = err.args
|
||
|
print ("%s: invalid directory: %s" % (dir, s))
|
||
|
sys.exit (1)
|
||
|
|
||
|
def get_driver_version():
|
||
|
global server_multicat
|
||
|
global driver_version
|
||
|
|
||
|
def run_with_arg (arg):
|
||
|
try:
|
||
|
p = subprocess.Popen ([driver_bin, arg],
|
||
|
stdin=subprocess.DEVNULL,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.DEVNULL,
|
||
|
shell=False)
|
||
|
(stdout, stderr) = p.communicate ()
|
||
|
except OSError:
|
||
|
return None
|
||
|
|
||
|
if stdout != None:
|
||
|
stdout = stdout.decode ()
|
||
|
|
||
|
return stdout
|
||
|
|
||
|
stdout = run_with_arg ("org.gutenprint.extensions")
|
||
|
if stdout == None:
|
||
|
return
|
||
|
for line in stdout.split ("\n"):
|
||
|
if line == "org.gutenprint.multicat":
|
||
|
server_multicat = 1
|
||
|
break
|
||
|
|
||
|
stdout = run_with_arg ("VERSION")
|
||
|
if stdout == None:
|
||
|
return
|
||
|
|
||
|
driver_version = stdout.strip ()
|
||
|
|
||
|
def parse_options():
|
||
|
try:
|
||
|
opts, args = getopt.getopt (sys.argv[1:], "d:hnqs:vNo:p:P:r:ifl:")
|
||
|
except getopt.GetoptError:
|
||
|
help ()
|
||
|
|
||
|
global optargs
|
||
|
global debug
|
||
|
global verbose
|
||
|
global interactive
|
||
|
global quiet
|
||
|
global no_action
|
||
|
global reset_defaults
|
||
|
global version
|
||
|
global micro_version
|
||
|
global use_static_ppd
|
||
|
global file_version
|
||
|
global ppd_dir
|
||
|
global ppd_out_dir
|
||
|
global ppd_base_dir
|
||
|
global ppd_root_dir
|
||
|
global serverdir
|
||
|
global driver_bin
|
||
|
global driver_version
|
||
|
global server_multicat
|
||
|
global languages
|
||
|
optargs = dict()
|
||
|
for opt, optarg in opts:
|
||
|
optargs[opt[1]] = optarg
|
||
|
|
||
|
if 'n' in optargs:
|
||
|
no_action = 1
|
||
|
|
||
|
if 'd' in optargs:
|
||
|
try:
|
||
|
debug = int (optargs['d'])
|
||
|
except ValueError:
|
||
|
d = 0
|
||
|
|
||
|
if 'v' in optargs:
|
||
|
verbose = 1
|
||
|
quiet = 0
|
||
|
|
||
|
if 'q' in optargs:
|
||
|
verbose = 0
|
||
|
quiet = 1
|
||
|
|
||
|
if 'N' in optargs:
|
||
|
reset_defaults = 1
|
||
|
|
||
|
if 'o' in optargs:
|
||
|
opt_o = optargs['o']
|
||
|
die_if_not_directory (opt_o)
|
||
|
ppd_out_dir = opt_o
|
||
|
|
||
|
if 'r' in optargs:
|
||
|
opt_r = optargs['r']
|
||
|
if version != opt_r:
|
||
|
version = opt_r
|
||
|
if 's' in optargs:
|
||
|
opt_s = optargs['s']
|
||
|
die_if_not_directory (opt_s)
|
||
|
ppd_base_dir = opt_s
|
||
|
driver_bin = ""
|
||
|
server_multicat = 0
|
||
|
use_static_ppd = "yes"
|
||
|
else:
|
||
|
ppd_base_dir = ppd_root_dir + "/gutenprint/" + version
|
||
|
driver_bin = serverdir + "/driver/gutenprint." + version
|
||
|
|
||
|
driver_version = ""
|
||
|
# If user specifies version, we're not going to be able to check
|
||
|
# for an exact match.
|
||
|
file_version = '"' + version
|
||
|
if os.access (driver_bin, os.X_OK):
|
||
|
get_driver_version ()
|
||
|
use_static_ppd = "no"
|
||
|
file_version = "\"%s\"$" % driver_version
|
||
|
else:
|
||
|
print ("Gutenprint %s does not appear to be installed!" %
|
||
|
version)
|
||
|
sys.exit (1)
|
||
|
|
||
|
if 's' in optargs:
|
||
|
opt_s = optargs['s']
|
||
|
die_if_not_directory (opt_s)
|
||
|
ppd_base_dir = opt_s
|
||
|
driver_bin = ""
|
||
|
server_multicat = 0
|
||
|
driver_version = ""
|
||
|
use_static_ppd = "yes"
|
||
|
|
||
|
if 'p' in optargs:
|
||
|
opt_p = optargs['p']
|
||
|
die_if_not_directory (opt_p)
|
||
|
ppd_dir = opt_p
|
||
|
|
||
|
if 'P' in optargs:
|
||
|
opt_P = optargs['P']
|
||
|
if os.access (opt_P, os.X_OK):
|
||
|
driver_bin = opt_P
|
||
|
get_driver_version ()
|
||
|
use_static_ppd = "no"
|
||
|
else:
|
||
|
print ("%s: invalid executable" % opt_P)
|
||
|
|
||
|
if 'h' in optargs:
|
||
|
help ()
|
||
|
|
||
|
if ('l' in optargs and
|
||
|
optargs['l'].lower () != "original" and
|
||
|
optargs['l'].lower () not in languages):
|
||
|
print ("Unknown language '%s'" % optargs['l'], file=sys.stderr)
|
||
|
|
||
|
if 'i' in optargs:
|
||
|
interactive = 1
|
||
|
|
||
|
if exit_after_parse_args:
|
||
|
sys.exit (0)
|
||
|
|
||
|
if verbose and driver_version != "":
|
||
|
print ("Updating PPD files from Gutenprint %s" % driver_version)
|
||
|
|
||
|
return args
|
||
|
|
||
|
def check_encoding(filename):
|
||
|
import charset_normalizer
|
||
|
|
||
|
with open(filename, 'rb') as f:
|
||
|
charenc = charset_normalizer.detect(f.read())['encoding']
|
||
|
|
||
|
if debug & 1:
|
||
|
print("File encoding: {}".format(charenc))
|
||
|
|
||
|
if charenc in ['ascii', 'utf-8']:
|
||
|
return 'utf-8'
|
||
|
else:
|
||
|
if debug & 1:
|
||
|
print("Trying to use latin1 for decoding {}".format(charenc))
|
||
|
|
||
|
return 'latin1'
|
||
|
|
||
|
def update_ppd (ppd_source_filename):
|
||
|
global ppd_dest_filename
|
||
|
global ppd_out_dir
|
||
|
global optargs
|
||
|
global languagemappings
|
||
|
global interactive
|
||
|
global server_multicat
|
||
|
global no_action
|
||
|
global quiet, verbose
|
||
|
global reset_defaults
|
||
|
|
||
|
ppd_dest_filename = ppd_source_filename
|
||
|
if ppd_out_dir:
|
||
|
ppd_dest_filename = "%s/%s" % (ppd_out_dir,
|
||
|
os.path.basename (ppd_dest_filename))
|
||
|
|
||
|
fenc = check_encoding(ppd_source_filename)
|
||
|
orig = open(ppd_source_filename, encoding=fenc)
|
||
|
orig_metadata = os.fstat (orig.fileno ())
|
||
|
if debug & 1:
|
||
|
print ("Source Filename: %s" % ppd_source_filename)
|
||
|
|
||
|
filename = ""
|
||
|
driver = ""
|
||
|
gutenprintdriver = ""
|
||
|
locale = ""
|
||
|
lingo = ""
|
||
|
region = ""
|
||
|
valid = 0
|
||
|
orig_locale = ""
|
||
|
|
||
|
try:
|
||
|
orig_lines = orig.readlines()
|
||
|
except UnicodeDecodeError:
|
||
|
if debug & 1:
|
||
|
print('PPD {} has an unexpected enconding, '
|
||
|
'skipping.'.format(ppd_source_filename))
|
||
|
|
||
|
return -1
|
||
|
|
||
|
for line in orig_lines:
|
||
|
line.rstrip ()
|
||
|
if line.find ("*StpLocale:") != -1:
|
||
|
match = re.search ("\*StpLocale:\s*\"(.*)\"$", line)
|
||
|
if match:
|
||
|
groups = match.groups ()
|
||
|
if len (groups) >= 1:
|
||
|
locale = groups[0]
|
||
|
orig_locale = locale
|
||
|
valid = 1
|
||
|
elif line.startswith ("*LanguageVersion"):
|
||
|
match = re.search ("^\*LanguageVersion:\s*(.*)$", line)
|
||
|
if match:
|
||
|
groups = match.groups ()
|
||
|
if len (groups) >= 1:
|
||
|
lingo = groups[0]
|
||
|
elif line.startswith ("*StpDriverName:"):
|
||
|
match = re.search ("^\*StpDriverName:\s*\"(.*)\"$", line)
|
||
|
if match:
|
||
|
groups = match.groups ()
|
||
|
if len (groups) >= 1:
|
||
|
driver = groups[0]
|
||
|
valid = 1
|
||
|
elif line.find ("*%End of ") != -1 and driver == "":
|
||
|
match = re.search ("^\*%End of\s*(.*).ppd$", line)
|
||
|
if match:
|
||
|
groups = match.groups ()
|
||
|
if len (groups) >= 1:
|
||
|
driver = groups[0]
|
||
|
elif line.startswith ("*StpPPDLocation:"):
|
||
|
match = re.search ("^\*StpPPDLocation:\s*\"(.*)\"$", line)
|
||
|
if match:
|
||
|
groups = match.groups ()
|
||
|
if len (groups) >= 1:
|
||
|
filename = groups[0]
|
||
|
valid = 1
|
||
|
elif line.startswith ("*%Gutenprint Filename:"):
|
||
|
valid = 1
|
||
|
|
||
|
if filename and driver and lingo and locale:
|
||
|
break
|
||
|
|
||
|
if not valid and line.startswith ("*OpenUI"):
|
||
|
break
|
||
|
|
||
|
if not valid:
|
||
|
#print (("Skipping %s: not a Gutenprint PPD file" %
|
||
|
# ppd_source_filename), file=sys.stderr)
|
||
|
return -1
|
||
|
|
||
|
if ('l' in optargs and
|
||
|
optargs['l'] != "" and
|
||
|
optargs['l'].lower () != "original"):
|
||
|
locale = optargs['l']
|
||
|
orig_locale = locale
|
||
|
|
||
|
if debug & 2:
|
||
|
print ("Gutenprint Filename: %s" % filename)
|
||
|
if 'l' in optargs:
|
||
|
print ("Locale: %s (from -l)" % locale)
|
||
|
else:
|
||
|
print ("Locale: %s" % locale)
|
||
|
|
||
|
print ("Language: %s" % lingo)
|
||
|
print ("Driver: %s" % driver)
|
||
|
|
||
|
if locale:
|
||
|
# Split into the language and territory.
|
||
|
s = locale.split ("_", 1)
|
||
|
locale = s[0]
|
||
|
try:
|
||
|
region = s[1]
|
||
|
except IndexError:
|
||
|
region = ""
|
||
|
else:
|
||
|
# Split into the language and territory.
|
||
|
s = lingo.split ("_", 1)
|
||
|
locale = s[0]
|
||
|
try:
|
||
|
region = s[1]
|
||
|
except IndexError:
|
||
|
region = ""
|
||
|
|
||
|
# Convert language into language code.
|
||
|
locale = languagemappings.get (lingo.lower (), "C")
|
||
|
|
||
|
if debug & 2:
|
||
|
print ("Base Locale: %s" % locale)
|
||
|
print ("Region: %s" % region)
|
||
|
|
||
|
# Read in the new PPD, decompressing it if needed...
|
||
|
(new_ppd_filename, source_fd) = get_ppd_fh (ppd_source_filename,
|
||
|
filename,
|
||
|
driver,
|
||
|
locale,
|
||
|
region)
|
||
|
if source_fd == None:
|
||
|
print ("Unable to retrieve PPD file!")
|
||
|
return 0
|
||
|
|
||
|
if interactive:
|
||
|
inp = input ("Update PPD %s from %s [nyq]? " % ppd_source_filename)
|
||
|
inp = inp.lower ()
|
||
|
if inp.startswith ("q"):
|
||
|
if server_multicat:
|
||
|
source_fd.detach ()
|
||
|
else:
|
||
|
source_fd.close ()
|
||
|
|
||
|
print ("Skipping all...")
|
||
|
return -2
|
||
|
elif not inp.startswith ("y"):
|
||
|
if server_multicat:
|
||
|
source_fd.detach ()
|
||
|
else:
|
||
|
source_fd.close ()
|
||
|
|
||
|
print ("Skipping...")
|
||
|
return -1
|
||
|
|
||
|
# Extract the default values from the original PPD...
|
||
|
|
||
|
orig.seek (0)
|
||
|
(odt, oopt, ores, odef, unused) = get_ppd_data (orig, 1, 0, 1, 1, 0)
|
||
|
(ndt, nopt, nres, ndef, source_data) = get_ppd_data (source_fd,
|
||
|
1, 1, 1, 1, 1)
|
||
|
|
||
|
# Close original and temporary files...
|
||
|
|
||
|
orig.close ()
|
||
|
if server_multicat:
|
||
|
source_fd.detach ()
|
||
|
else:
|
||
|
source_fd.close ()
|
||
|
|
||
|
orig_default_types = odt
|
||
|
new_default_types = ndt
|
||
|
defaults = odef
|
||
|
new_defaults = ndef
|
||
|
options = nopt
|
||
|
resolution_map = nres
|
||
|
old_resolution_map = dict()
|
||
|
for key, value in resolution_map.items ():
|
||
|
old_resolution_map[value] = key
|
||
|
|
||
|
# Store previous language in the PPD file so that -l original works
|
||
|
# correctly.
|
||
|
|
||
|
if orig_locale != "":
|
||
|
lines = source_data.rstrip ().split ("\n")
|
||
|
source_data = ""
|
||
|
for line in lines:
|
||
|
m = re.search ("(\*StpLocale:\s*\")(.*)(\")", line)
|
||
|
if m:
|
||
|
groups = m.groups ()
|
||
|
line = groups[0] + orig_locale + groups[2]
|
||
|
|
||
|
source_data += line + "\n"
|
||
|
|
||
|
if debug & 4:
|
||
|
print ("Options (Old->New Default Type):")
|
||
|
keys = list(options.keys ())
|
||
|
keys.sort ()
|
||
|
for t in keys:
|
||
|
old_type = orig_default_types.get (t, "(New)")
|
||
|
new_type = new_default_types.get (t)
|
||
|
if old_type != new_type:
|
||
|
out = " %s (%s -> %s) : " % (t, old_type, new_type)
|
||
|
else:
|
||
|
out = " %s (%s) : " % (t, new_type)
|
||
|
|
||
|
dft = defaults.get ("Default%s" % t)
|
||
|
for opt in options.get (t, []):
|
||
|
if dft != None and dft == opt:
|
||
|
out += "*"
|
||
|
|
||
|
out += "%s " % opt
|
||
|
|
||
|
print (out)
|
||
|
|
||
|
if len (list(resolution_map.keys ())) > 0:
|
||
|
print ("Resolution Map:")
|
||
|
keys = list(resolution_map.keys ())
|
||
|
keys.sort ()
|
||
|
for key in keys:
|
||
|
print (" %s: %s" % (key, resolution_map[key]))
|
||
|
|
||
|
if len (list(old_resolution_map.keys ())) > 0:
|
||
|
print ("Old Resolution Map:")
|
||
|
keys = list(old_resolution_map.keys ())
|
||
|
keys.sort ()
|
||
|
for key in keys:
|
||
|
print (" %s: %s" % (key, old_resolution_map[key]))
|
||
|
|
||
|
print ("Non-UI Defaults:")
|
||
|
keys = list(defaults.keys ())
|
||
|
keys.sort ()
|
||
|
for key in keys:
|
||
|
xkey = key
|
||
|
if xkey.startswith ("Default"):
|
||
|
xkey = xkey[7:]
|
||
|
if xkey not in options:
|
||
|
print (" %s: %s" % (key, defaults[key]))
|
||
|
|
||
|
print ("Default Types of dropped options:")
|
||
|
keys = list(orig_default_types.keys ())
|
||
|
keys.sort ()
|
||
|
for t in keys:
|
||
|
if t not in options:
|
||
|
print (" %s: %s" % (t, orig_default_types[t]))
|
||
|
|
||
|
if no_action:
|
||
|
if not quiet or verbose:
|
||
|
if ppd_dest_filename == ppd_source_filename:
|
||
|
print ("Would update %s using %s" % (ppd_source_filename,
|
||
|
new_ppd_filename))
|
||
|
else:
|
||
|
print ("Would update %s to %s using %s" % (ppd_source_filename,
|
||
|
ppd_dest_filename,
|
||
|
new_ppd_filename))
|
||
|
|
||
|
return 0
|
||
|
|
||
|
if not reset_defaults:
|
||
|
# Update source buffer with old defaults...
|
||
|
|
||
|
# Loop through each default in turn.
|
||
|
keys = list(defaults.keys ())
|
||
|
keys.sort ()
|
||
|
for default_option in keys:
|
||
|
default_option_value = defaults[default_option]
|
||
|
option = default_option
|
||
|
if option.startswith ("Default"):
|
||
|
# Strip off `Default'
|
||
|
option = option[7:]
|
||
|
|
||
|
# Check method is valid
|
||
|
orig_method = orig_default_types.get (option)
|
||
|
new_method = new_default_types.get (option)
|
||
|
new_default = new_defaults.get (default_option)
|
||
|
if (orig_method == None or new_method == None or
|
||
|
orig_method != new_method):
|
||
|
continue
|
||
|
|
||
|
if (new_default != None and
|
||
|
default_option_value == new_default):
|
||
|
if verbose:
|
||
|
print ("%s: Preserve *%s (%s)" % (ppd_source_filename,
|
||
|
default_option,
|
||
|
default_option_value))
|
||
|
|
||
|
continue
|
||
|
|
||
|
if new_method == "PickOne":
|
||
|
next_default = False
|
||
|
|
||
|
# Check the old setting is valid
|
||
|
for opt in options.get (option, []):
|
||
|
def_option = default_option_value
|
||
|
odef_option = def_option
|
||
|
if (option == "Resolution" and
|
||
|
def_option in old_resolution_map):
|
||
|
if debug & 4:
|
||
|
print (("Intermapping old resolution %s to %s" %
|
||
|
def_option, old_resolution_map[def_option]))
|
||
|
|
||
|
def_option = old_resolution_map[def_option]
|
||
|
|
||
|
dopts = [def_option]
|
||
|
if def_option != odef_option:
|
||
|
dopts.append (odef_option)
|
||
|
|
||
|
for dopt in dopts:
|
||
|
valid = False
|
||
|
if dopt == opt:
|
||
|
valid = True
|
||
|
elif (option == "Resolution" and
|
||
|
dopt in resolution_map):
|
||
|
dopt = resolution_map[dopt]
|
||
|
if dopt == opt:
|
||
|
valid = True
|
||
|
|
||
|
if valid:
|
||
|
# Valid option
|
||
|
|
||
|
# Set the option in the new PPD
|
||
|
lines = source_data.rstrip ().split ("\n")
|
||
|
source_data = ""
|
||
|
attr = "*%s" % default_option
|
||
|
for line in lines:
|
||
|
if line.startswith (attr):
|
||
|
line = "%s:%s" % (attr, dopt)
|
||
|
|
||
|
source_data += line + "\n"
|
||
|
|
||
|
if verbose:
|
||
|
print ("%s: Set *%s to %s" %
|
||
|
(ppd_source_filename,
|
||
|
default_option,
|
||
|
dopt))
|
||
|
|
||
|
next_default = True
|
||
|
break
|
||
|
if next_default:
|
||
|
break
|
||
|
|
||
|
if next_default:
|
||
|
continue
|
||
|
|
||
|
print (("Warning: %s: Invalid option: *%s: %s. Using default "
|
||
|
"setting %s." % (ppd_source_filename, default_option,
|
||
|
defaults[default_option],
|
||
|
new_defaults[default_option])))
|
||
|
continue
|
||
|
|
||
|
print (("Warning: %s: PPD OpenUI method %s not understood." %
|
||
|
(ppd_source_filename, new_default_types[default_option])))
|
||
|
|
||
|
# Write new PPD...
|
||
|
tmpnew = "%s.new" % ppd_dest_filename
|
||
|
try:
|
||
|
newppd = open (tmpnew, "w")
|
||
|
except IOError as err:
|
||
|
(e, s) = err.args
|
||
|
print ("Can't create %s: %s" % (tmpnew, s))
|
||
|
return 0
|
||
|
|
||
|
newppd.writelines (source_data)
|
||
|
try:
|
||
|
newppd.close ()
|
||
|
except IOError as err:
|
||
|
(e, s) = err.args
|
||
|
print ("Can't write to %s: %s" % (tmpnew, s))
|
||
|
return 0
|
||
|
|
||
|
chcon = subprocess.Popen (["chcon", "--reference=%s" % ppd_dest_filename,
|
||
|
tmpnew], shell=False,
|
||
|
stdin=subprocess.DEVNULL,
|
||
|
stdout=subprocess.DEVNULL,
|
||
|
stderr=subprocess.STDOUT)
|
||
|
chcon.communicate ()
|
||
|
|
||
|
try:
|
||
|
os.rename (tmpnew, ppd_dest_filename)
|
||
|
except OSError as err:
|
||
|
(e, s) = err.args
|
||
|
print ("Can't rename %s to %s: %s" % (tmpnew, ppd_dest_filename, s))
|
||
|
try:
|
||
|
os.unlink (tmpnew)
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
return 0
|
||
|
|
||
|
try:
|
||
|
os.chown (ppd_dest_filename,
|
||
|
orig_metadata.st_uid,
|
||
|
orig_metadata.st_gid)
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
try:
|
||
|
os.chmod (ppd_dest_filename,
|
||
|
orig_metadata.st_mode & 0o777)
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
if not quiet or verbose:
|
||
|
if ppd_dest_filename == ppd_source_filename:
|
||
|
print ("Updated %s using %s" % (ppd_source_filename,
|
||
|
new_ppd_filename))
|
||
|
else:
|
||
|
print ("Updated %s to %s using %s" % (ppd_source_filename,
|
||
|
ppd_dest_filename,
|
||
|
new_ppd_filename))
|
||
|
|
||
|
# All done!
|
||
|
return 1
|
||
|
|
||
|
def get_ppd_data (fh, types, opts, resolutions, defaults, data):
|
||
|
options_map = dict()
|
||
|
defaults_map = dict()
|
||
|
resolution_map = dict()
|
||
|
default_types = dict()
|
||
|
cur_opt = ""
|
||
|
optionlist = []
|
||
|
source_data = ""
|
||
|
|
||
|
if reset_defaults:
|
||
|
types = 0
|
||
|
opts = 0
|
||
|
resolutions = 0
|
||
|
defaults = 0
|
||
|
|
||
|
if resolutions or types or opts or defaults or data:
|
||
|
while True:
|
||
|
line = fh.readline ()
|
||
|
if line == '':
|
||
|
break
|
||
|
if line == "*%*%EOFEOF\n":
|
||
|
break
|
||
|
source_data += line
|
||
|
line = line.strip ()
|
||
|
|
||
|
if (types or opts) and line.startswith ("*OpenUI"):
|
||
|
m = re.search ("^\*OpenUI\s\*(\w+).*:\s(\w+)",
|
||
|
line)
|
||
|
if m:
|
||
|
groups = m.groups ()
|
||
|
key = groups[0]
|
||
|
value = groups[1]
|
||
|
default_types[key] = value
|
||
|
cur_opt = key
|
||
|
elif opts and line.startswith ("*CloseUI"):
|
||
|
if cur_opt != "":
|
||
|
options_map[cur_opt] = optionlist
|
||
|
cur_opt = ""
|
||
|
|
||
|
optionlist = []
|
||
|
elif opts and line.startswith ("*%s" % cur_opt):
|
||
|
m = re.search ("^\*%s\s*(\w+)[\/:]" % cur_opt, line)
|
||
|
if m:
|
||
|
groups = m.groups()
|
||
|
if len (groups) >= 1:
|
||
|
value = m.groups ()[0]
|
||
|
optionlist.append (value)
|
||
|
elif resolutions and line.startswith ("*StpResolutionMap:"):
|
||
|
s = line.split (None, 3)
|
||
|
if len (s) == 3:
|
||
|
new = s[1]
|
||
|
old = s[2]
|
||
|
resolution_map[old] = new
|
||
|
elif defaults and line.startswith ("*Default"):
|
||
|
m = re.search ("^\*(\w+):\s*(\w+)", line)
|
||
|
if m:
|
||
|
groups = m.groups ()
|
||
|
key = groups[0]
|
||
|
value = groups[1]
|
||
|
defaults_map[key] = value
|
||
|
|
||
|
return (default_types, options_map, resolution_map,
|
||
|
defaults_map, source_data)
|
||
|
|
||
|
def get_ppd_fh (ppd_source_filename, filename, driver, locale, region):
|
||
|
global use_static_ppd
|
||
|
global driver_version
|
||
|
global optargs
|
||
|
global driver_bin
|
||
|
global debug
|
||
|
global server_multicat, server_multicat_initialized
|
||
|
global gzext
|
||
|
|
||
|
if use_static_ppd == "no" and driver_version != "":
|
||
|
if re.search (".*/([^/]*)(.sim)(.ppd)?(.gz)?$", filename):
|
||
|
simplified = "simple"
|
||
|
else:
|
||
|
simplified = "expert"
|
||
|
|
||
|
opt_r = optargs.get ('r')
|
||
|
if opt_r:
|
||
|
try:
|
||
|
opt_r = float (opt_r)
|
||
|
except ValueError:
|
||
|
opt_r = None
|
||
|
|
||
|
url_list = []
|
||
|
if (((opt_r != None and opt_r < 5.2) or
|
||
|
('l' in optargs and optargs['l'] != "")) and
|
||
|
locale != ""):
|
||
|
if region:
|
||
|
url_list.append ("gutenprint.%s://%s/%s/%s_%s" %
|
||
|
version, driver, simplified, locale, region)
|
||
|
url_list.append ("gutenprint.%s://%s/%s/%s" %
|
||
|
version, driver, simplified, locale)
|
||
|
|
||
|
url_list.append ("gutenprint.%s://%s/%s" % (version, driver,
|
||
|
simplified))
|
||
|
for url in url_list:
|
||
|
new_ppd_filename = url
|
||
|
if debug & 8:
|
||
|
if server_multicat:
|
||
|
cat = ""
|
||
|
else:
|
||
|
cat = "%s cat " % driver_bin
|
||
|
|
||
|
print (("Trying %s%s for %s, %s, %s, %s" %
|
||
|
(cat, url, driver, simplified, locale, region)))
|
||
|
|
||
|
if server_multicat:
|
||
|
try:
|
||
|
if not server_multicat_initialized:
|
||
|
mc_proc = subprocess.Popen ([driver_bin,
|
||
|
"org.gutenprint.multicat"],
|
||
|
shell=False,
|
||
|
stdin=subprocess.PIPE,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.DEVNULL)
|
||
|
server_multicat_initialized = mc_proc
|
||
|
|
||
|
mc_in = io.TextIOWrapper (server_multicat_initialized.stdin)
|
||
|
print ("%s" % url, file=mc_in)
|
||
|
mc_in.flush ()
|
||
|
mc_in.detach ()
|
||
|
return (new_ppd_filename,
|
||
|
io.TextIOWrapper (server_multicat_initialized.stdout))
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
try:
|
||
|
proc = subprocess.Popen ([driver_bin, "cat", url],
|
||
|
shell=False,
|
||
|
stdin=subprocess.DEVNULL,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.DEVNULL)
|
||
|
return (new_ppd_filename, io.TextIOWrapper (proc.stdout))
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
# Otherwise fall through and try to find a static PPD
|
||
|
|
||
|
# Search for a PPD matching our criteria...
|
||
|
|
||
|
new_ppd_filename = find_ppd (filename, driver, locale, region)
|
||
|
if not new_ppd_filename:
|
||
|
# There wasn't a valid source PPD file, so give up.
|
||
|
print (("%s: no valid candidate for replacement. Skipping" %
|
||
|
ppd_source_filename), file=sys.stderr)
|
||
|
print (("%s: please upgrade this PPD manually" %
|
||
|
ppd_source_filename), file=sys.stderr)
|
||
|
return ("", None)
|
||
|
|
||
|
if debug & 1:
|
||
|
print ("Candidate PPD: %s" % new_ppd_filename)
|
||
|
|
||
|
suffix = "\\" + gzext # Add '\' so the regexp matches the '.'
|
||
|
if new_ppd_filename.endswith (".gz"):
|
||
|
# Decompress input buffer
|
||
|
try:
|
||
|
proc = subprocess.Popen (['gunzip', '-c', new_ppd_filename],
|
||
|
shell=False,
|
||
|
stdin=subprocess.DEVNULL,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.DEVNULL)
|
||
|
except OSError as err:
|
||
|
(e, s) = err.args
|
||
|
print ("can't open for decompression: %s" % s)
|
||
|
sys.exit (1)
|
||
|
|
||
|
return (new_ppd_filename, io.TextIOWrapper (proc.stdout))
|
||
|
else:
|
||
|
return (new_ppd_filename, open (new_ppd_filename))
|
||
|
|
||
|
def find_ppd (gutenprintfilename, drivername, lang, region):
|
||
|
global file_version
|
||
|
global optargs
|
||
|
global ppd_base_dir
|
||
|
global ppd_root_dir
|
||
|
global debug
|
||
|
|
||
|
key = '^\\*FileVersion:[ ]*' + file_version
|
||
|
match = re.search (".*/([^/]+\.[0-9]+\.[0-9]+)(\.sim)?(\.ppd)?(\.gz)?$",
|
||
|
gutenprintfilename)
|
||
|
if not match:
|
||
|
return None
|
||
|
|
||
|
stored_name = match.groups ()[0]
|
||
|
if re.search (".*/([^/]*)(\.sim)(\.ppd)?(\.gz)?$", gutenprintfilename):
|
||
|
simplified = ".sim"
|
||
|
else:
|
||
|
simplified = ""
|
||
|
|
||
|
stored_dir = os.path.dirname (gutenprintfilename)
|
||
|
|
||
|
current_best_file = ""
|
||
|
current_best_time = 0
|
||
|
if 's' in optargs:
|
||
|
basedirs = [optargs['s']]
|
||
|
else:
|
||
|
basedirs = [ppd_base_dir, stored_dir, ppd_root_dir]
|
||
|
|
||
|
lingos = []
|
||
|
if region != "":
|
||
|
lingos.append ("%s_%s/" % (lang, region))
|
||
|
|
||
|
lingos.append ("%s/" % lang)
|
||
|
if lang != "C":
|
||
|
lingos.append ("C/")
|
||
|
|
||
|
lingos.append ("en/")
|
||
|
lingos.append ("")
|
||
|
lingos.append ("Global/")
|
||
|
bases = ["stp-%s.%s%s" % (drivername, version, simplified),
|
||
|
"%s.%s%s" % (drivername, version, simplified)]
|
||
|
if stored_name not in bases:
|
||
|
bases.append (stored_name)
|
||
|
|
||
|
bases.append (drivername)
|
||
|
|
||
|
# All possible candidates, in order of usefulness and gzippedness
|
||
|
for lingo in lingos:
|
||
|
for suffix in (".ppd%s" % gzext,
|
||
|
".ppd"):
|
||
|
for base in bases:
|
||
|
for basedir in basedirs:
|
||
|
if basedir == "" or base == "":
|
||
|
continue
|
||
|
|
||
|
fn = "%s/%s%s%s" % (basedir, lingo, base, suffix)
|
||
|
if debug & 8:
|
||
|
print (("Trying %s for %s, %s, %s" %
|
||
|
(fn, gutenprintfilename, lang, region)))
|
||
|
|
||
|
try:
|
||
|
st = os.stat (fn)
|
||
|
except OSError:
|
||
|
continue
|
||
|
|
||
|
if ('f' in optargs or
|
||
|
(stat.S_ISREG (st.st_mode) and
|
||
|
st.st_uid == 0)):
|
||
|
# Check that the file is a valid Gutenprint PPD file
|
||
|
# of the correct version.
|
||
|
if fn.endswith (".gz"):
|
||
|
cmdline = "gunzip -c '%s' | grep '%s'" % (fn, key)
|
||
|
else:
|
||
|
cmdline = "cat '%s' | grep '%s'" % (fn, key)
|
||
|
|
||
|
try:
|
||
|
p = subprocess.Popen (cmdline,
|
||
|
stdin=subprocess.DEVNULL,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.DEVNULL)
|
||
|
except OSError:
|
||
|
new_file_version = ""
|
||
|
else:
|
||
|
(stdin, stderr) = p.communicate ()
|
||
|
new_file_version = stdin.decode ().rstrip ()
|
||
|
|
||
|
if new_file_version != "":
|
||
|
if debug & 8:
|
||
|
print ((" Format valid: time %s best %s "
|
||
|
"prev %s cur %s!" %
|
||
|
(st.st_mtime, current_best_time,
|
||
|
current_best_file, fn)))
|
||
|
|
||
|
if st.st_mtime > current_best_time:
|
||
|
current_best_time = st.st_mtime
|
||
|
current_best_file = fn
|
||
|
if debug & 8:
|
||
|
print (("***current_best_file "
|
||
|
" is %s" % fn), file=sys.stderr)
|
||
|
elif debug & 8:
|
||
|
print (" Format invalid")
|
||
|
else:
|
||
|
if (not stat.S_ISDIR (st.st_mode) and
|
||
|
not fn.endswith ("/")):
|
||
|
print (("%s: not a regular file, "
|
||
|
"or insecure ownership and "
|
||
|
"permissions. Skipped" % fn),
|
||
|
file=sys.stderr)
|
||
|
|
||
|
if current_best_file:
|
||
|
return current_best_file
|
||
|
|
||
|
# Yikes! Cannot find a valid PPD file!
|
||
|
return None
|
||
|
|
||
|
debug=0
|
||
|
verbose=0
|
||
|
interactive=0
|
||
|
quiet=0
|
||
|
no_action=0
|
||
|
reset_defaults=0
|
||
|
version="@GUTENPRINT_MAJOR_VERSION@.@GUTENPRINT_MINOR_VERSION@"
|
||
|
micro_version="@GUTENPRINT_VERSION@"
|
||
|
use_static_ppd="@BUILD_CUPS_PPDS@"
|
||
|
file_version='"@VERSION@"$'
|
||
|
|
||
|
ppd_dir = "@cups_conf_serverroot@/ppd"
|
||
|
ppd_root_dir = "@cups_conf_datadir@/model";
|
||
|
ppd_base_dir = ppd_root_dir + "/gutenprint/" + version
|
||
|
ppd_out_dir = ""
|
||
|
gzext = ".gz"
|
||
|
updated_ppd_count = 0
|
||
|
skipped_ppd_count = 0
|
||
|
failed_ppd_count = 0
|
||
|
exit_after_parse_args = 0
|
||
|
languages=["Global", "C"] + "@ALL_LINGUAS@".split (' ')
|
||
|
|
||
|
serverdir = "@cups_conf_serverbin@"
|
||
|
driver_bin = serverdir + "/driver/gutenprint." + version
|
||
|
driver_version = ""
|
||
|
server_multicat = 0
|
||
|
server_multicat_initialized = 0
|
||
|
|
||
|
if os.access (driver_bin, os.X_OK):
|
||
|
get_driver_version ()
|
||
|
|
||
|
ppd_files = []
|
||
|
languagemappings = { "chinese": "cn",
|
||
|
"danish": "da",
|
||
|
"dutch": "nl",
|
||
|
"english": "en",
|
||
|
"finnish": "fi",
|
||
|
"french": "fr",
|
||
|
"german": "de",
|
||
|
"greek": "el",
|
||
|
"hungarian": "hu",
|
||
|
"italian": "it",
|
||
|
"japanese": "jp",
|
||
|
"norwegian": "no",
|
||
|
"polish": "pl",
|
||
|
"portuguese": "pt",
|
||
|
"russian": "ru",
|
||
|
"slovak": "sk",
|
||
|
"spanish": "es",
|
||
|
"swedish": "sv",
|
||
|
"turkish": "tr" }
|
||
|
|
||
|
# Check command-line options...
|
||
|
args = parse_options()
|
||
|
|
||
|
# Set a secure umask...
|
||
|
os.umask (0o177)
|
||
|
|
||
|
|
||
|
# Find all in-use Gutenprint PPD files...
|
||
|
# For case-insensitive filesystems, use only one of .ppd and .PPD
|
||
|
# (bug 1929738)
|
||
|
|
||
|
for f in args:
|
||
|
if (os.access (f, os.F_OK) and
|
||
|
(f.lower ().endswith (".ppd") or
|
||
|
f.find ("/") != -1)):
|
||
|
ppd_files.append (f)
|
||
|
elif os.access ("%s/%s" % (ppd_dir, f), os.F_OK):
|
||
|
ppd_files.append ("%s/%s" % (ppd_dir, f))
|
||
|
elif os.access ("%s/%s.ppd" % (ppd_dir, f), os.F_OK):
|
||
|
ppd_files.append ("%s/%s.ppd" % (ppd_dir, f))
|
||
|
elif os.access ("%s/%s.PPD" % (ppd_dir, f), os.F_OK):
|
||
|
ppd_files.append ("%s/%s.PPD" % (ppd_dir, f))
|
||
|
else:
|
||
|
print (("Cannot find file %s/%s, %s/%s.ppd, or %s/%s.PPD" %
|
||
|
ppd_dir, f, ppd_dir, f, ppd_dir, f), file=sys.stderr)
|
||
|
|
||
|
if len (args) == 0:
|
||
|
ppdtmp = glob.glob ("%s/*.ppd" % ppd_dir)
|
||
|
ppdtmp += glob.glob ("%s/*.PPD" % ppd_dir)
|
||
|
ppd_map = dict()
|
||
|
for each in ppdtmp:
|
||
|
ppd_map[each] = 1
|
||
|
|
||
|
for f in ppdtmp:
|
||
|
if f.endswith (".PPD"):
|
||
|
g = f[:-4] + ".ppd"
|
||
|
if g not in ppd_map:
|
||
|
ppd_files.append (f)
|
||
|
else:
|
||
|
ppd_files.append (f)
|
||
|
|
||
|
# Update each of the Gutenprint PPDs, where possible...
|
||
|
|
||
|
for ppd_file in ppd_files:
|
||
|
status = update_ppd (ppd_file)
|
||
|
if status == -2:
|
||
|
break
|
||
|
elif status == 0:
|
||
|
failed_ppd_count += 1
|
||
|
elif status == 1:
|
||
|
updated_ppd_count += 1
|
||
|
elif status == -1:
|
||
|
skipped_ppd_count += 1
|
||
|
|
||
|
if (not quiet) or verbose:
|
||
|
if len (ppd_files) == 0:
|
||
|
print ("No Gutenprint PPD files to update.")
|
||
|
elif updated_ppd_count > 0:
|
||
|
plural = ""
|
||
|
if updated_ppd_count != 1:
|
||
|
plural = "s"
|
||
|
|
||
|
print ("Updated %d PPD file%s" % (updated_ppd_count, plural))
|
||
|
if (('o' not in optargs) or
|
||
|
optargs['o'] != ""):
|
||
|
print ("Restart cupsd for the changes to take effect.")
|
||
|
else:
|
||
|
if failed_ppd_count > 0:
|
||
|
print ("Failed to update any PPD files")
|
||
|
else:
|
||
|
print ("Did not update any PPD files")
|
||
|
|
||
|
sys.exit (failed_ppd_count > 0)
|