Merge lp:~brendan-donegan/checkbox/bug1066094 into lp:checkbox

Proposed by Brendan Donegan
Status: Work in progress
Proposed branch: lp:~brendan-donegan/checkbox/bug1066094
Merge into: lp:checkbox
Diff against target: 171 lines (+65/-23)
2 files modified
debian/changelog (+2/-0)
scripts/network_restart (+63/-23)
To merge this branch: bzr merge lp:~brendan-donegan/checkbox/bug1066094
Reviewer Review Type Date Requested Status
Marc Tardif (community) Needs Fixing
Review via email: mp+129880@code.launchpad.net

Commit message

Fix bug 1066094 by dealing with each case where superuser is needed in network_restart individually

Description of the change

As pointed out in the linked bug, the network_restart script took a very brute-force approach to informing the user of the script that superuser permissions may be required for specific operations - i.e. it assumes that they are and doesn't allow the script to be run without them. This has been changed so that for each command that may require superuser permissions, if an error occurs then a check is done to see if the script is not being run as the superuser and the user of the script is informed if that's the case.

To post a comment you must log in.
1778. By Brendan Donegan

Tweaks to the code.

Revision history for this message
Marc Tardif (cr3) wrote :

> - output = check_output(['/sbin/ifconfig', '-s', '-a'])
> + output = check_output(['ifconfig', '-s', '-a'])

Thank you for changing the path to ifconfig and the other commands from
absolute to just their names!

> - for interface in self.interfaces:
> + for iface in self.interfaces:

What was wrong with the name "interface"?

> try:
> - check_output(['/sbin/ifconfig', interface, 'up'])
> + check_output(['ifconfig', iface, 'up'])
> except CalledProcessError:

This will still output the stderr of ifconfig to the terminal. The
exception raised by the check_output function only has one output
attribute, so you need to pipe stderr into stdout:

    check_output(
        ['/sbin/ifconfig', interface, 'up'],
        stderr=subprocess.STDOUT)

> - logging.error('Unable to bring up interface {0!r}'
> - .format(interface))
> + message = 'Unable to bring up interface {0!r}'.format(iface)
> +
> + if os.getuid():
> + message += self.SUPERUSER_MSG
> +
> + message += '.'
> +
> + print(message, file=sys.stderr)
> + logging.error(message)
> raise

How do you know that just because check_output raised a CalledProcessError
that it was because of missing superuser privileges? Here's another error
that has nothing to do with privileges:

    $ ifconfig eth8 up
    eth8: ERROR while getting interface flags: No such device

So, once you piped stderr into stdout, you should use the output variable
in the exception to provide a useful and accurate error message to the
user:

    message = "Unable to bring up interface {0!r}:\n".format(interface)
    message += exception.output.decode("utf-8")
    print(message, file=sys.stderr)
    logging.error(message)
    raise

the same applies for the other calls that assumer that any error is because
of missing superuser privileges.

> - default='/var/log',
> + default='.',
> help='The path to the log directory. \
> Default is /var/log')

The problem here is that the default kwarg was changed but the help wasn't
changed accordingly. The solution would be to use %(default)s in the help
instead of hard coding a duplicate value.

Revision history for this message
Marc Tardif (cr3) wrote :

See above comment, I wonder how I can set the review to "needs fixing" by email...

review: Needs Fixing
Revision history for this message
Brendan Donegan (brendan-donegan) wrote :

On 16/10/12 14:52, Marc Tardif wrote:
>> - output = check_output(['/sbin/ifconfig', '-s', '-a'])
>> + output = check_output(['ifconfig', '-s', '-a'])
> Thank you for changing the path to ifconfig and the other commands from
> absolute to just their names!
You're welcome :)
>
>> - for interface in self.interfaces:
>> + for iface in self.interfaces:
> What was wrong with the name "interface"?
Not a lot, just that it avoids having to split certain statements across
multiple lines. I reckoned iface is a fairly common contraction of
interface so it shouldn't be confusing
>
>> try:
>> - check_output(['/sbin/ifconfig', interface, 'up'])
>> + check_output(['ifconfig', iface, 'up'])
>> except CalledProcessError:
> This will still output the stderr of ifconfig to the terminal. The
> exception raised by the check_output function only has one output
> attribute, so you need to pipe stderr into stdout:
This is an existing problem with the script as far as I know, but it
would be ok to fix it now.
>
> check_output(
> ['/sbin/ifconfig', interface, 'up'],
> stderr=subprocess.STDOUT)
>
>> - logging.error('Unable to bring up interface {0!r}'
>> - .format(interface))
>> + message = 'Unable to bring up interface {0!r}'.format(iface)
>> +
>> + if os.getuid():
>> + message += self.SUPERUSER_MSG
>> +
>> + message += '.'
>> +
>> + print(message, file=sys.stderr)
>> + logging.error(message)
>> raise
> How do you know that just because check_output raised a CalledProcessError
> that it was because of missing superuser privileges? Here's another error
> that has nothing to do with privileges:
>
> $ ifconfig eth8 up
> eth8: ERROR while getting interface flags: No such device
>
> So, once you piped stderr into stdout, you should use the output variable
> in the exception to provide a useful and accurate error message to the
> user:
>
> message = "Unable to bring up interface {0!r}:\n".format(interface)
> message += exception.output.decode("utf-8")
> print(message, file=sys.stderr)
> logging.error(message)
> raise
Sounds like a fine method, I'll give it a try
>
> the same applies for the other calls that assumer that any error is because
> of missing superuser privileges.
>
>> - default='/var/log',
>> + default='.',
>> help='The path to the log directory. \
>> Default is /var/log')
> The problem here is that the default kwarg was changed but the help wasn't
> changed accordingly. The solution would be to use %(default)s in the help
> instead of hard coding a duplicate value.
I fixed this specific issue but in principle where defaults are
mentioned in help text that convention should be used, so I'll change it.
>

Revision history for this message
Brendan Donegan (brendan-donegan) wrote :

Rather disappointingly, the contents of exception.output in the case of 'start network-manager' failing are:

'Unable to stop network-manager, stop: Rejected send message, 1 matched rules; type="method_call", sender=":1.159" (uid=1000 pid=20099 comm="stop network-manager ") interface="com.ubuntu.Upstart0_6.Job" member="Stop" error name="(unset)" requested_reply="0" destination="com.ubuntu.Upstart" (uid=0 pid=1 comm="/sbin/init")\n'

Not exactly helpful. I appreciate the observation that we shouldn't assume that lack of superuser privileges caused a command to fail, but if this is the alternative then it's not really helpful.

Revision history for this message
Marc Tardif (cr3) wrote :

As mentionned in #ubuntu-server, I would suggest we special case upstart errors:

  when the output string contains "Rejected send message", change the message to invalid permission
  otherwise, use the original message

Revision history for this message
Brendan Donegan (brendan-donegan) wrote :

I don't have time to work on this right now and it's a very low priority fix

Unmerged revisions

1778. By Brendan Donegan

Tweaks to the code.

1777. By Brendan Donegan

scripts/network_restart - be specific about where superuser permissions
are required (LP: #1066094)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/changelog'
--- debian/changelog 2012-10-15 20:03:32 +0000
+++ debian/changelog 2012-10-16 13:20:27 +0000
@@ -7,6 +7,8 @@
7 don't get tampered with (LP: #1065932)7 don't get tampered with (LP: #1065932)
8 * plugins/launchpad_exchange.py - Remove call to string_to_type on8 * plugins/launchpad_exchange.py - Remove call to string_to_type on
9 self.timeout, which is an int (LP: #1066967)9 self.timeout, which is an int (LP: #1066967)
10 * scripts/network_restart - be specific about where superuser permissions
11 are required (LP: #1066094)
1012
11 [Daniel Manrique]13 [Daniel Manrique]
12 * [FEATURE] checkbox/job.py: Fixed intltool warning about unnamed14 * [FEATURE] checkbox/job.py: Fixed intltool warning about unnamed
1315
=== modified file 'scripts/network_restart'
--- scripts/network_restart 2012-10-12 20:16:20 +0000
+++ scripts/network_restart 2012-10-16 13:20:27 +0000
@@ -21,6 +21,7 @@
21except (ImportError, RuntimeError):21except (ImportError, RuntimeError):
22 gtk_found = False22 gtk_found = False
2323
24SUPERUSER_MSG = ", superuser permissions required"
2425
25class PingError(Exception):26class PingError(Exception):
26 def __init__(self, address, reason):27 def __init__(self, address, reason):
@@ -31,12 +32,6 @@
31def main():32def main():
32 args = parse_args()33 args = parse_args()
3334
34 # Verify that script is run as root
35 if os.getuid():
36 sys.stderr.write(
37 'This script needs superuser permissions to run correctly\n')
38 return 1
39
40 configure_logging(args.log_level, args.output)35 configure_logging(args.log_level, args.output)
4136
42 # Select interface based on graphich capabilities available37 # Select interface based on graphich capabilities available
@@ -196,6 +191,8 @@
196 """191 """
197 Networking abstraction to start/stop all interfaces192 Networking abstraction to start/stop all interfaces
198 """193 """
194
195
199 def __init__(self, address):196 def __init__(self, address):
200 self.address = address197 self.address = address
201 self.interfaces = self._get_interfaces()198 self.interfaces = self._get_interfaces()
@@ -204,7 +201,7 @@
204 """201 """
205 Get all network interfaces202 Get all network interfaces
206 """203 """
207 output = check_output(['/sbin/ifconfig', '-s', '-a'])204 output = check_output(['ifconfig', '-s', '-a'])
208 lines = output.splitlines()[1:]205 lines = output.splitlines()[1:]
209 interfaces = ([interface for interface in206 interfaces = ([interface for interface in
210 [line.split()[0] for line in lines] if interface != 'lo'])207 [line.split()[0] for line in lines] if interface != 'lo'])
@@ -222,19 +219,34 @@
222 Start networking219 Start networking
223 """220 """
224 logging.info('Bringing all interfaces up...')221 logging.info('Bringing all interfaces up...')
225 for interface in self.interfaces:222 for iface in self.interfaces:
226 try:223 try:
227 check_output(['/sbin/ifconfig', interface, 'up'])224 check_output(['ifconfig', iface, 'up'])
228 except CalledProcessError:225 except CalledProcessError:
229 logging.error('Unable to bring up interface {0!r}'226 message = 'Unable to bring up interface {0!r}'.format(iface)
230 .format(interface))227
228 if os.getuid():
229 message += SUPERUSER_MSG
230
231 message += '.'
232
233 print(message, file=sys.stderr)
234 logging.error(message)
231 raise235 raise
232236
233 logging.info('Starting network manager...')237 logging.info('Starting network manager...')
234 try:238 try:
235 check_output(['/sbin/start', 'network-manager'])239 check_output(['start', 'network-manager'])
236 except CalledProcessError:240 except CalledProcessError:
237 logging.error('Unable to start network manager')241 message = 'Unable to stop network-manager'
242
243 if os.getuid():
244 message += SUPERUSER_MSG
245
246 message += "."
247
248 print(message)
249 logging.error(message)
238 raise250 raise
239251
240 # Verify that network interface is up252 # Verify that network interface is up
@@ -245,8 +257,7 @@
245 if success:257 if success:
246 break258 break
247 else:259 else:
248 raise PingError(self.address,260 raise PingError(self.address, 'Some interface is still down')
249 'Some interface is still down')
250261
251 def _stop(self):262 def _stop(self):
252 """263 """
@@ -254,18 +265,33 @@
254 """265 """
255 logging.info('Stopping network manager...')266 logging.info('Stopping network manager...')
256 try:267 try:
257 check_output(['/sbin/stop', 'network-manager'])268 check_output(['stop', 'network-manager'])
258 except CalledProcessError:269 except CalledProcessError:
259 logging.error('Unable to stop network manager')270 message = 'Unable to stop network-manager'
271
272 if os.getuid():
273 message += SUPERUSER_MSG
274
275 message += "."
276
277 print(message, file=sys.stderr)
278 logging.error(message)
260 raise279 raise
261280
262 logging.info('Bringing all interfaces down...')281 logging.info('Bringing all interfaces down...')
263 for interface in self.interfaces:282 for iface in self.interfaces:
264 try:283 try:
265 check_output(['/sbin/ifconfig', interface, 'down'])284 check_output(['ifconfig', iface, 'down'])
266 except CalledProcessError:285 except CalledProcessError:
267 logging.error('Unable to bring down interface {0!r}'286 message = "Unable to bring down interface {0!r}".format(iface)
268 .format(interface))287
288 if os.getuid():
289 message += SUPERUSER_MSG
290
291 message += "."
292
293 print(message, file=sys.stderr)
294 logging.error(message)
269 raise295 raise
270296
271 # Verify that network interface is down297 # Verify that network interface is down
@@ -327,8 +353,22 @@
327 log_filename = os.path.join(output,353 log_filename = os.path.join(output,
328 '{0}.log'.format(os.path.basename(__file__)))354 '{0}.log'.format(os.path.basename(__file__)))
329 rollover = os.path.exists(log_filename)355 rollover = os.path.exists(log_filename)
330 log_handler = logging.handlers.RotatingFileHandler(log_filename, mode='a+',356
331 backupCount=3)357 try:
358 log_handler = logging.handlers.RotatingFileHandler(log_filename,
359 mode='a+',
360 backupCount=3)
361 except IOError:
362 message = 'Unable to log to {0}'.format(log_filename)
363
364 if os.getuid():
365 message += SUPERUSER_MSG
366
367 message += '.'
368
369 print(message)
370 return
371
332 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')372 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
333 log_handler.setFormatter(formatter)373 log_handler.setFormatter(formatter)
334 log_handler.setLevel(logging.DEBUG)374 log_handler.setLevel(logging.DEBUG)

Subscribers

People subscribed via source and target branches