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