1#!/usr/local/bin/python3.8
2
3## Printing troubleshooter
4
5## Copyright (C) 2008, 2010, 2014 Red Hat, Inc.
6## Authors:
7##  Tim Waugh <twaugh@redhat.com>
8
9## This program is free software; you can redistribute it and/or modify
10## it under the terms of the GNU General Public License as published by
11## the Free Software Foundation; either version 2 of the License, or
12## (at your option) any later version.
13
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17## GNU General Public License for more details.
18
19## You should have received a copy of the GNU General Public License
20## along with this program; if not, write to the Free Software
21## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22
23from gi.repository import Gtk
24
25import cups
26import os
27from tempfile import NamedTemporaryFile
28import datetime
29import time
30from timedops import TimedOperation
31from .base import *
32
33try:
34    from systemd import journal
35except:
36    journal = False
37
38class ErrorLogFetch(Question):
39    def __init__ (self, troubleshooter):
40        Question.__init__ (self, troubleshooter, "Error log fetch")
41        page = self.initial_vbox (_("Retrieve Journal Entries"),
42                                  _("No system journal entries were found. "
43                                    "This may be because you are not an "
44                                    "administrator. To fetch journal entries "
45                                    "please run this command:"))
46        self.entry = Gtk.Entry ()
47        self.entry.set_editable (False)
48        page.pack_start (self.entry, False, False, 0)
49        troubleshooter.new_page (page, self)
50        self.persistent_answers = {}
51
52    def display (self):
53        answers = self.troubleshooter.answers
54        parent = self.troubleshooter.get_window ()
55        self.answers = {}
56        checkpoint = answers.get ('error_log_checkpoint')
57        cursor = answers.get ('error_log_cursor')
58        timestamp = answers.get ('error_log_timestamp')
59
60        if ('error_log' in self.persistent_answers or
61            'journal' in self.persistent_answers):
62            checkpoint = None
63            cursor = None
64
65        def fetch_log (c):
66            prompt = c._get_prompt_allowed ()
67            c._set_prompt_allowed (False)
68            c._connect ()
69            with NamedTemporaryFile (delete=False) as tmpf:
70                success = False
71                try:
72                    c.getFile ('/admin/log/error_log', file = tmpf)
73                    success = True
74                except cups.HTTPError:
75                    try:
76                        os.remove (tmpf.name)
77                    except OSError:
78                        pass
79
80                c._set_prompt_allowed (prompt)
81                if success:
82                    return tmpf.file
83
84            return None
85
86        now = datetime.datetime.fromtimestamp (time.time ()).strftime ("%F %T")
87        self.authconn = self.troubleshooter.answers['_authenticated_connection']
88        if 'error_log_debug_logging_set' in answers:
89            try:
90                self.op = TimedOperation (self.authconn.adminGetServerSettings,
91                                          parent=parent)
92                settings = self.op.run ()
93            except cups.IPPError:
94                return False
95
96            settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0'
97            orig_settings = answers['cups_server_settings']
98            settings['MaxLogSize'] = orig_settings.get ('MaxLogSize', '2000000')
99            success = False
100            def set_settings (connection, settings):
101                connection.adminSetServerSettings (settings)
102
103                # Now reconnect.
104                attempt = 1
105                while attempt <= 5:
106                    try:
107                        time.sleep (1)
108                        connection._connect ()
109                        break
110                    except RuntimeError:
111                        # Connection failed
112                        attempt += 1
113
114            try:
115
116                self.op = TimedOperation (set_settings,
117                                          (self.authconn, settings),
118                                          parent=parent)
119                self.op.run ()
120                self.persistent_answers['error_log_debug_logging_unset'] = True
121            except cups.IPPError:
122                pass
123
124        self.answers = {}
125        if journal and cursor is not None:
126            def journal_format (x):
127                try:
128                    priority = "XACEWNIDd"[x['PRIORITY']]
129                except (IndexError, TypeError):
130                    priority = " "
131
132                return (priority + " " +
133                        x['__REALTIME_TIMESTAMP'].strftime("[%m/%b/%Y:%T]") +
134                        " " + x['MESSAGE'])
135
136            r = journal.Reader ()
137            r.seek_cursor (cursor)
138            r.add_match (_SYSTEMD_UNIT="cups.service")
139            self.answers['journal'] = [journal_format (x) for x in r]
140
141        if checkpoint is not None:
142            self.op = TimedOperation (fetch_log,
143                                      (self.authconn,),
144                                      parent=parent)
145            tmpfname = self.op.run ()
146            if tmpfname is not None:
147                f = open (tmpfname)
148                f.seek (checkpoint)
149                lines = f.readlines ()
150                os.remove (tmpfname)
151                self.answers = { 'error_log': [x.strip () for x in lines] }
152
153        if (len (self.answers.get ('journal', [])) +
154            len (self.answers.get ('error_log', []))) == 0:
155            cmd = ("su -c 'journalctl -u cups.service "
156                   "--since=\"%s\" --until=\"%s\"' > troubleshoot-logs.txt" %
157                   (timestamp, now))
158            self.entry.set_text (cmd)
159            return True
160
161        return False
162
163    def collect_answer (self):
164        answers = self.persistent_answers.copy ()
165        answers.update (self.answers)
166        return answers
167
168    def cancel_operation (self):
169        self.op.cancel ()
170
171        # Abandon the CUPS connection and make another.
172        answers = self.troubleshooter.answers
173        factory = answers['_authenticated_connection_factory']
174        self.authconn = factory.get_connection ()
175        self.answers['_authenticated_connection'] = self.authconn
176