Logiciel Libre

November 28, 2006

Lesson Learned: sudo failing for X11 apps

Filed under: Default — adam @ 11:57 pm UTC

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.

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment

Powered by WordPress