Lesson Learned: sudo failing for X11 apps
I was recently getting the following error when trying to use GUI administration tools bundled with Fedora Core:
$ sudo system-config-printer Xlib: connection to ":0.0" refused by server Xlib: No protocol specified system-config-printer: could not open display This is a graphical application and requires DISPLAY to be set.
Sounds like fun!
This error is commonly associated with a failure to propagate the XAUTHORITY environment variable into the execution environment subshell (or whatever sudo fires up). The sudoers file was messed up because I had upgraded the ’sudo’ package, and RPM kindly left behind /etc/sudoers.rpmnew to let me know that my sudo configuration needed upgrading, but it wasn’t going to figure out how to upgrade the config file and also merge my changes in, I’d have to do it manually.
sudo should probably be architected in a way that makes upgrading simpler, but for now I’m stuck with manually merging the configuration file. After a quick scan of the filesystem, I noticed that sudo wasn’t the only thing to leave behind .rpmnew and .rpmsave artifacts, so I needed a little something to help me walk through and fix these bummers. Python to the rescue.
#!/usr/bin/python
#################### LICENSE ####################################
# rpmnew_resolve. Aid user in cleaning up .rpmnew/.rpmsave files
# Copyright (C) 2006 Adam Monsen
#
# 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
# of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#################################################################
# When RPMs are upgraded, some files (especially config files that require
# hand-editing) need manual merging. This script should slightly simplify the
# process of resolving these upgrade leftovers. Ideally, the RPMs would contain
# code to gracefully handle merging, or obliviate the need for merging by
# architecting the software differently.
# $Id: rpmnew_resolve 1952 2006-12-09 15:05:25Z adamm $
from threading import Thread
import os
import re
import shutil
import sys
import tempfile
import time
if sys.version_info < (2, 3):
print "Requires Python version 2.3 or greater, sorry."
sys.exit(1)
active_conf_re = re.compile(r'\\.rpm(new|save)$')
DONE_EXT = '.rresolved'
exit_message = None
class FindConflicts(Thread):
def __init__(self):
Thread.__init__(self)
self.cmd = 'find / -name *.rpmnew -type f -or -name *.rpmsave -type f 2>/dev/null'
def run(self):
self.rpmnew_list = os.popen(self.cmd).read()
conflict_finder = FindConflicts()
print 'Generating file list.',
conflict_finder.start()
while conflict_finder.isAlive():
sys.stdout.write('.')
sys.stdout.flush()
time.sleep(1)
print 'DONE.'
wd = tempfile.mkdtemp()
for conflict in conflict_finder.rpmnew_list.split():
active_conf = active_conf_re.sub('', conflict)
if not os.path.exists(active_conf):
print >> sys.stderr, "active_conf missing! cleaning up %s" % conflict
cmd = 'sudo mv %s %s%s' % (conflict, active_conf, DONE_EXT)
if os.system(cmd) is not 0:
print '%s FAILED. Continuing...' % cmd
continue
print "\\nCONFLICT: %s, ACTIVE: %s" % (conflict, active_conf)
# get lines of difference.
# If < 4 lines, display (in unified format)
# else, ask user if they want to see the diff (default to No)
cmd = 'sudo diff %s %s' % (active_conf, conflict)
diffoutput = os.popen(cmd).read()
difflines = diffoutput.split('\\n')
display_diff = False
show_vimdiff = False
if len(difflines) < 10:
display_diff = True
else:
print "diff output is very large. Skipping."
if display_diff:
print "%s OUTPUT..." % cmd
print diffoutput
# temp dir made at script start is used for all resolutions.
# use a simply-named temp dir within this global temp dir for this
# particular resolution operation.
b_conflict = os.path.basename(conflict)
b_active_conf = os.path.basename(active_conf)
r_tempdir = os.path.join(wd, b_active_conf)
os.mkdir(r_tempdir)
t_conflict = os.path.join(wd, r_tempdir, b_conflict)
t_active_conf = os.path.join(wd, r_tempdir, b_active_conf)
# make temp "conflict" file for possible editing
cmd = 'sudo cp %s %s' % (conflict, t_conflict)
if os.system(cmd) is not 0:
exit_message = "conflict tmpfile creation FAILED. Aborting..."
break
# make temp "active" file for possible editing
cmd = 'sudo cp %s %s' % (active_conf, t_active_conf)
if os.system(cmd) is not 0:
exit_message = "active_conf tmpfile creation FAILED. Aborting..."
break
# ask user if they want to visually merge these files
confirm = 'x'
while confirm.lower() not in ('y','n','q',''):
prompt = 'Start visual merge? (y/N/q):'
confirm = raw_input(prompt)
if confirm.lower() == 'q':
break
elif confirm.lower() == 'y':
print "Visually merging using temporary files.",
print "You will have a chance to abort this operation."
cmd = 'sudo vimdiff %s %s' % (t_active_conf, t_conflict)
if os.system(cmd) is not 0:
exit_message = "vimdiff FAILED. Aborting..."
break
# assess user actions
cmd = 'sudo diff -q %s %s > /dev/null' % (t_active_conf, t_conflict)
rv_system = os.WEXITSTATUS(os.system(cmd))
if rv_system is 0:
print "Files are identical."
elif rv_system is 1:
print "Files are different."
else:
exit_message = "Error diffing [%d]. Aborting..." % rv_system
break
# prompt user for next step
confirm = 'x'
enable_conf = 'x'
while confirm.lower() not in ('o','m','c','s','q',''):
prompt = "What now? (o=keep old/m=use merged/c=use conflict/S=skip/q=quit):"
confirm = raw_input(prompt)
if confirm.lower() == 'o':
enable_conf = 'old'
elif confirm.lower() == 'm':
enable_conf = 'merged'
elif confirm.lower() == 'c':
enable_conf = 'conflict'
elif confirm.lower() in ('s', ''):
continue
elif confirm.lower() == 'q':
break
resolved_fname = active_conf + DONE_EXT
if enable_conf == 'old':
print "Keeping current %s." % active_conf
cmd = 'sudo mv -f %s %s' % (conflict, resolved_fname)
if os.system(cmd) is not 0:
exit_message = 'cmd [%s] FAILED. Aborting...' % cmd
break
elif enable_conf == 'merged':
print "Overwriting %s with %s." % (active_conf, t_active_conf)
cmd = 'sudo mv -f %s %s' % (active_conf, resolved_fname)
if os.system(cmd) is not 0:
exit_message = 'cmd [%s] FAILED. Aborting...' % cmd
break
cmd = 'sudo cp -f %s %s' % (t_active_conf, active_conf)
if os.system(cmd) is not 0:
exit_message = 'cmd [%s] FAILED. Aborting...' % cmd
break
cmd = 'sudo rm -f %s' % conflict
if os.system(cmd) is not 0:
exit_message = 'cmd [%s] FAILED. Aborting...' % cmd
break
elif enable_conf == 'conflict':
print "Overwriting %s with %s." % (active_conf, conflict)
cmd = 'sudo mv -f %s %s' % (active_conf, resolved_fname)
if os.system(cmd) is not 0:
exit_message = 'cmd [%s] FAILED. Aborting...' % cmd
break
cmd = 'sudo mv -f %s %s' % (conflict, active_conf)
if os.system(cmd) is not 0:
exit_message = 'cmd [%s] FAILED. Aborting...' % cmd
break
print "Stored %s." % resolved_fname
shutil.rmtree(wd)
sys.exit(exit_message)
Ok, so, yeah, that got WAY out of hand. And it’s nasty: it’s dependent on Python 2.4, find, mv, cp, diff, etc. But it does the job and it was fun to write.
And now, your moment of Zen:
Open source is free like a puppy is free
-Scott McNealy. Chairman of the Board, Sun Microsystems, Inc.