December 2005 Archives

Unexpected side-effect

| No Comments

I discovered a strange bug in my Python code recently. It took me a few minutes worth of digging to uncover something somewhat surprising, a side-effect to do with default parameters that is not obvious at first blush.

Consider the following code:

import sys

class Group:

def__init__(self, name, desc='', questions=[]):
self.name = name
self.desc = desc
self.questions = questions

def__repr__(self):
return'''Group:
Name: %s
Description: %s
Questions: %s
'''
% (self.name, self.desc, ', '.join(self.questions))

deftest():
g1 = Group('foo')
g2 = Group('baz', 'a really bazzy group')
g3 = Group('bar', 'some barry groups', ['first q', 'secundo', 'tri'])

g4 = Group('vroom')

if len(sys.argv) > 1:
g4.questions.append('This is not a question')

print g1
print g2
print g3
print g4

if __name__ == '__main__':
test()

If run with no parameters, it appears to have the expected and desired behaviour. Give it a parameter, and the line that appends a question will cause all instances to have the same question added. Obviously a bug, but why?

What's happening is the default value for the question parameter is evaluated at compile time to be an instance of an empty list. All instances then refer to this same empty list, so when one gets something appended, they all appear to get it. The solution is of course to have it default to None and then construct a list when required.

Even when the default parameter is a function, it is still evaluated only the once, so its return value becomes a constant default value. It turns out that this is clearly documented (thanks Fred!) in the Python Language Reference: Section 7.5 Function definitions.

For someone used to doing funky stuff in C++ constructors (like yours truly), this might be a subtle gotcha.

Dropping Privileges in Python

| 4 Comments


When writing a small web application in Cherrypy I needed the server to run on port 80. Of course running a server as root is enough to scare even the hardest sysadmin, so I obviously wanted it to drop privs immediately upon startup, once it had opened the default http port. I wrote the function below to drop privs and switch to a new user and group (usually nobody/nogroup). It is self-contained, should work with anything - there is nothing specific to any system. If you find it useful of have any suggestions to improve it, please leave a comment.

To use this within Cherrypy, set the port to 80 in the cherrypy config file, then add the following before your server start:

cpg.server.onStartServerList = [drop_privileges]

drop_privileges.py

import logging

log = logging.getLogger('server')
logging.basicConfig()
logging.root.setLevel(level=logging.INFO)

# ...

def drop_privileges(uid_name='nobody', gid_name='nogroup'):

import os, pwd, grp

starting_uid = os.getuid()
starting_gid = os.getgid()

starting_uid_name = pwd.getpwuid(starting_uid)[0]

log.info('drop_privileges: started as %s/%s' % \
(pwd.getpwuid(starting_uid)[0],
grp.getgrgid(starting_gid)[0]))

if os.getuid() != 0:
# We're not root so, like, whatever dude
log.info("drop_privileges: already running as '%s'"%starting_uid_name)
return

# If we started as root, drop privs and become the specified user/group
if starting_uid == 0:

# Get the uid/gid from the name
running_uid = pwd.getpwnam(uid_name)[2]
running_gid = grp.getgrnam(gid_name)[2]

# Try setting the new uid/gid
try:
os.setgid(running_gid)
except OSError, e:
log.error('Could not set effective group id: %s' % e)

try:
os.setuid(running_uid)
except OSError, e:
log.error('Could not set effective user id: %s' % e)

# Ensure a very convervative umask
new_umask = 077
old_umask = os.umask(new_umask)
log.info('drop_privileges: Old umask: %s, new umask: %s' % \
(oct(old_umask), oct(new_umask)))

final_uid = os.getuid()
final_gid = os.getgid()
log.info('drop_privileges: running as %s/%s' % \
(pwd.getpwuid(final_uid)[0],
grp.getgrgid(final_gid)[0]))

# Test it
if __name__ == '__main__':
drop_privileges()

Getting started with Cocoa development

| No Comments

Some notes on random stuff I found out the hard way.

How to rename a NIB in XCode?

You don't. You do a SaveAs in Interface Builder, give it a new name, then delete the old one. Then you have to go into your class and change -(NSString*)windowNibName to return the name of the new file (minus the extension). Why this can't be done from within XCode is beyond me.

How to add a drawer to an existing window?

Create an NSDrawer object and then a custom view object. The custom view becomes the drawer content. Connect the NSDrawer's contentView to the custom view and parentWindow to the existing window.

How to add a toolbar to a main window?

Create a NSDrawer, set contentView to an NSCustomView and parentWindow to the actual main window. Then you can add a reveal button, by...

How to put controls into a split view?

Not shown in palette. Create objects first, then select them. Choose Layout|Make SubviewsOf|SplitView.

How to add an ellipsis "..." to a menu item?

Type Option-; (that is, semicolon).

Renaming a widget instance (such as a controller) in IB?

Double-click on the icon text itself and edit directly. (This is not at all obvious, since right-clicking (or control-clicking or whatever) doesn't reveal anything, and there doesn't seem to be a way to do it via the menu either.)

How to change the application icon (NSApplicationIcon)?

The default A icon is part of the bundle. To change it, create an image (ideally 128x128) and open up Icon Composer (from /Developer/Utilities). Paste in your image into the main icon size (and others as you wish) then save the icns file into your project. Go into XCode and add the file to your project (using Project|Add). Then select the application Target, then bring up the info window (Command-I). In the Properties tab, enter the filename (minus the .icns extension) of the icon you just saved. Rebuild and voila!