1# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import subprocess
6
7# Full path of the CUPS configuration file
8_CUPS_CONF_FILE = '/etc/cups/cupsd.conf'
9
10class CommandFailedException(Exception):
11    """
12    An simple exception that is thrown when OS command fails.
13
14    """
15    pass
16
17class Configurator():
18    """
19    An instance of this class is responsible for an initial configuration of
20    the system. This is performed by the method configure(). To restore the
21    system to the state before a configure() call, the method  restore() must
22    be called.
23
24    """
25
26    def __init__(self):
27        """
28        Constructor.
29
30        """
31        self._cupsd_conf_loglevel_line_no = None
32        self._cupsd_conf_loglevel_content = None
33
34
35    def _run_as_root(self, argv):
36        """
37        Run given command as root.
38
39        @param argv: an array of command-line parameters
40
41        @returns standard output produced by the command
42
43        @raises Exception if the command returns code different than 0
44
45        """
46        p1 = subprocess.Popen(["echo", "test0000"], stdout=subprocess.PIPE)
47        p2 = subprocess.Popen(["sudo", "--stdin", "--prompt="] + argv,
48                stdin=p1.stdout, stdout=subprocess.PIPE)
49        p1.stdout.close()
50        out,err = p2.communicate()
51        if p2.returncode != 0:
52            raise CommandFailedException("The command '%s' returns %d" %
53                    (' '.join(argv),p2.returncode));
54        return out
55
56
57    def _set_cups_logging_level(self):
58        """
59        Modify the CUPS configuration file to set log level to 'debug'.
60
61        @raises Exception in case of any errors
62
63        """
64        # parse content of the CUPS configuration file and find a number of
65        # a line with 'LogLevel' option
66        lines = self._run_as_root(["cat", _CUPS_CONF_FILE]).splitlines()
67
68        for index, line in enumerate(lines):
69            if line.startswith('LogLevel'):
70                line_no = index
71                break
72        if line_no is None:
73            raise Exception('Cannot find a line with LogLevel in cupsd.conf')
74        # save the original line and replace it with 'LogLevel debug'
75        self._cupsd_conf_loglevel_content = lines[line_no]
76        self._cupsd_conf_loglevel_line_no = line_no + 1
77        self._run_as_root(['sed', '-i', '%ds/.*/LogLevel debug/' % (line_no+1),
78                _CUPS_CONF_FILE])
79        # if CUPS is started, we have to stop
80        try:
81            self._run_as_root(['stop', 'cupsd'])
82        except CommandFailedException:
83            pass
84
85
86    def _restore_cups_logging_level(self):
87        """
88        Restore content of the CUPS configuration file to this one before
89        calling _set_cups_logging_level(). Do nothing if the method
90        _set_cups_logging_level() was not called earlier.
91
92        """
93        if self._cupsd_conf_loglevel_content is None:
94            return
95        self._run_as_root(['sed', '-i', '%ds/.*/%s/' %
96                (self._cupsd_conf_loglevel_line_no,
97                self._cupsd_conf_loglevel_content), _CUPS_CONF_FILE])
98        self._cupsd_conf_loglevel_content = None
99        self._cupsd_conf_loglevel_line_no = None
100
101
102    def _set_root_partition_as_read_write(self):
103        """
104        Remount the root partition in read-write mode.
105
106        """
107        self._run_as_root(['mount', '-o', 'rw,remount', '/'])
108
109
110    def configure(self, set_cups_logging_level):
111        """
112        Apply the configuration required by the test.
113
114        @param set_cups_logging_level: True or False; if True then
115                the root partition is remounted in R/W mode and the CUPS
116                configuration file is updated to set "LogLevel" to "debug".
117        """
118        # Update CUPS logging level
119        if set_cups_logging_level:
120            self._set_root_partition_as_read_write()
121            self._set_cups_logging_level()
122
123
124    def restore(self):
125        """
126        Restore the system state before configure(). It is safe to run
127        this method, even if configure() failed or has not been called.
128
129        """
130        # Restore CUPS logging level
131        if self._cupsd_conf_loglevel_content is not None:
132            self._restore_cups_logging_level()
133