~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: drtomc
  • Date: 2008-01-02 22:21:12 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:128
A couple of bug fixes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
# Date:   12/12/2007
22
22
 
23
23
# This is a command-line application, for use by the administrator.
24
 
# This program is a frontend for the modules in the setup packages that 
25
 
# configure, build and install IVLE in three separate steps.
 
24
# This program configures, builds and installs IVLE in three separate steps.
26
25
# It is called with at least one argument, which specifies which operation to
27
26
# take.
28
27
 
 
28
# setup.py listmake (for developer use only)
 
29
# Recurses through the source tree and builds a list of all files which should
 
30
# be copied upon installation. This should be run by the developer before
 
31
# cutting a distribution, and the listfile it generates should be included in
 
32
# the distribution, avoiding the administrator having to run it.
 
33
 
 
34
# setup.py conf [args]
 
35
# Configures IVLE with machine-specific details, most notably, various paths.
 
36
# Either prompts the administrator for these details or accepts them as
 
37
# command-line args.
 
38
# Creates www/conf/conf.py and trampoline/conf.h.
 
39
 
 
40
# setup.py build
 
41
# Compiles all files and sets up a jail template in the source directory.
 
42
# Details:
 
43
# Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
 
44
# Creates jail/.
 
45
# Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
 
46
# Copies console/ to a location within the jail.
 
47
# Copies OS programs and files to corresponding locations within the jail
 
48
#   (eg. python and Python libs, ld.so, etc).
 
49
# Generates .pyc files for all the IVLE .py files.
 
50
 
 
51
# setup.py install [--nojail] [--dry|n]
 
52
# (Requires root)
 
53
# Create target install directory ($target).
 
54
# Create $target/bin.
 
55
# Copy trampoline/trampoline to $target/bin.
 
56
# chown and chmod the installed trampoline.
 
57
# Copy www/ to $target.
 
58
# Copy jail/ to jails template directory (unless --nojail specified).
 
59
 
 
60
import os
 
61
import stat
 
62
import shutil
29
63
import sys
30
 
import setup.configure
31
 
import setup.build
32
 
import setup.install
33
 
 
 
64
import getopt
 
65
import string
 
66
import errno
 
67
import mimetypes
 
68
import compileall
 
69
import getopt
 
70
 
 
71
# Try importing existing conf, but if we can't just set up defaults
 
72
# The reason for this is that these settings are used by other phases
 
73
# of setup besides conf, so we need to know them.
 
74
# Also this allows you to hit Return to accept the existing value.
 
75
try:
 
76
    confmodule = __import__("www/conf/conf")
 
77
    root_dir = confmodule.root_dir
 
78
    ivle_install_dir = confmodule.ivle_install_dir
 
79
    jail_base = confmodule.jail_base
 
80
except ImportError:
 
81
    # Just set reasonable defaults
 
82
    root_dir = "/ivle"
 
83
    ivle_install_dir = "/opt/ivle"
 
84
    jail_base = "/home/informatics/jails"
 
85
# Always defaults
 
86
allowed_uids = "0"
 
87
 
 
88
# Try importing install_list, but don't fail if we can't, because listmake can
 
89
# function without it.
 
90
try:
 
91
    import install_list
 
92
except:
 
93
    pass
 
94
 
 
95
# Mime types which will automatically be placed in the list by listmake.
 
96
# Note that listmake is not intended to be run by the final user (the system
 
97
# administrator who installs this), so the developers can customize the list
 
98
# as necessary, and include it in the distribution.
 
99
listmake_mimetypes = ['text/x-python', 'text/html',
 
100
    'application/x-javascript', 'application/javascript',
 
101
    'text/css', 'image/png']
 
102
 
 
103
# Main function skeleton from Guido van Rossum
 
104
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
34
105
 
35
106
def main(argv=None):
36
107
    if argv is None:
39
110
    # Print the opening spiel including the GPL notice
40
111
 
41
112
    print """IVLE - Informatics Virtual Learning Environment Setup
42
 
Copyright (C) 2007-2009 The University of Melbourne
 
113
Copyright (C) 2007-2008 The University of Melbourne
43
114
IVLE comes with ABSOLUTELY NO WARRANTY.
44
115
This is free software, and you are welcome to redistribute it
45
116
under certain conditions. See LICENSE.txt for details.
55
126
        help([])
56
127
        return 1
57
128
 
58
 
    oper_func = call_operator(operation)
59
 
    return oper_func(argv[2:])
60
 
 
61
 
def help(args):
62
 
    if len(args)!=1:
63
 
        print """Usage: python setup.py operation [options]
64
 
Operation can be:
65
 
    help [operation]
66
 
    config
67
 
    build
68
 
    install
69
 
 
70
 
    For help and options for a specific operation use 'help [operation]'."""
71
 
    else:
72
 
        operator = args[0]
73
 
        oper_func = call_operator(operator)
74
 
        oper_func(['operator','--help'])
75
 
 
76
 
def call_operator(operation):
77
129
    # Call the requested operation's function
78
130
    try:
79
131
        oper_func = {
80
132
            'help' : help,
81
 
            'config' : setup.configure.configure,
82
 
            'build' : setup.build.build,
83
 
            'install' : setup.install.install,
84
 
            #'updatejails' : None,
 
133
            'conf' : conf,
 
134
            'build' : build,
 
135
            'listmake' : listmake,
 
136
            'install' : install,
85
137
        }[operation]
86
138
    except KeyError:
87
139
        print >>sys.stderr, (
88
140
            """Invalid operation '%s'. Try python setup.py help."""
89
141
            % operation)
90
 
        sys.exit(1)
91
 
    return oper_func
 
142
    return oper_func(argv[2:])
 
143
 
 
144
# Operation functions
 
145
 
 
146
def help(args):
 
147
    if args == []:
 
148
        print """Usage: python setup.py operation [args]
 
149
Operation (and args) can be:
 
150
    help [operation]
 
151
    conf [args]
 
152
    build
 
153
    install [--nojail] [-n|--dry]
 
154
"""
 
155
        return 1
 
156
    elif len(args) != 1:
 
157
        print """Usage: python setup.py help [operation]"""
 
158
        return 2
 
159
    else:
 
160
        operation = args[0]
 
161
 
 
162
    if operation == 'help':
 
163
        print """python setup.py help [operation]
 
164
Prints the usage message or detailed help on an operation, then exits."""
 
165
    elif operation == 'listmake':
 
166
        print """python setup.py listmake
 
167
(For developer use only)
 
168
Recurses through the source tree and builds a list of all files which should
 
169
be copied upon installation. This should be run by the developer before
 
170
cutting a distribution, and the listfile it generates should be included in
 
171
the distribution, avoiding the administrator having to run it."""
 
172
    elif operation == 'conf':
 
173
        print """python setup.py conf [args]
 
174
Configures IVLE with machine-specific details, most notably, various paths.
 
175
Either prompts the administrator for these details or accepts them as
 
176
command-line args. Will be interactive only if there are no arguments given.
 
177
Takes defaults from existing conf file if it exists.
 
178
Creates www/conf/conf.py and trampoline/conf.h.
 
179
Args are:
 
180
    --root_dir
 
181
    --ivle_install_dir
 
182
    --jail_base
 
183
    --allowed_uids
 
184
As explained in the interactive prompt or conf.py.
 
185
"""
 
186
    elif operation == 'build':
 
187
        print """python -O setup.py build [--dry|-n]
 
188
Compiles all files and sets up a jail template in the source directory.
 
189
-O is recommended to cause compilation to be optimised.
 
190
Details:
 
191
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
 
192
Creates jail/.
 
193
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
 
194
Copies console/ to a location within the jail.
 
195
Copies OS programs and files to corresponding locations within the jail
 
196
  (eg. python and Python libs, ld.so, etc).
 
197
Generates .pyc or .pyo files for all the IVLE .py files.
 
198
 
 
199
--dry | -n  Print out the actions but don't do anything."""
 
200
    elif operation == 'install':
 
201
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
202
(Requires root)
 
203
Create target install directory ($target).
 
204
Create $target/bin.
 
205
Copy trampoline/trampoline to $target/bin.
 
206
chown and chmod the installed trampoline.
 
207
Copy www/ to $target.
 
208
Copy jail/ to jails template directory (unless --nojail specified).
 
209
 
 
210
--nojail    Do not copy the jail.
 
211
--dry | -n  Print out the actions but don't do anything."""
 
212
    else:
 
213
        print >>sys.stderr, (
 
214
            """Invalid operation '%s'. Try python setup.py help."""
 
215
            % operation)
 
216
    return 1
 
217
 
 
218
def listmake(args):
 
219
    # We build two separate lists, by walking www and console
 
220
    list_www = build_list_py_files('www')
 
221
    list_console = build_list_py_files('console')
 
222
    # Make sure that the files generated by conf are in the list
 
223
    # (since listmake is typically run before conf)
 
224
    if "www/conf/conf.py" not in list_www:
 
225
        list_www.append("www/conf/conf.py")
 
226
    # Make sure that console/python-console is in the list
 
227
    if "console/python-console" not in list_console:
 
228
        list_console.append("console/python-console")
 
229
    # Write these out to a file
 
230
    cwd = os.getcwd()
 
231
    # the files that will be created/overwritten
 
232
    listfile = os.path.join(cwd, "install_list.py")
 
233
 
 
234
    try:
 
235
        file = open(listfile, "w")
 
236
 
 
237
        file.write("""# IVLE Configuration File
 
238
# install_list.py
 
239
# Provides lists of all files to be installed by `setup.py install' from
 
240
# certain directories.
 
241
# Note that any files with the given filename plus 'c' or 'o' (that is,
 
242
# compiled .pyc or .pyo files) will be copied as well.
 
243
 
 
244
# List of all installable files in www directory.
 
245
list_www = """)
 
246
        writelist_pretty(file, list_www)
 
247
        file.write("""
 
248
# List of all installable files in console directory.
 
249
list_console = """)
 
250
        writelist_pretty(file, list_console)
 
251
 
 
252
        file.close()
 
253
    except IOError, (errno, strerror):
 
254
        print "IO error(%s): %s" % (errno, strerror)
 
255
        sys.exit(1)
 
256
 
 
257
    print "Successfully wrote install_list.py"
 
258
 
 
259
    print
 
260
    print ("You may modify the set of installable files before cutting the "
 
261
            "distribution:")
 
262
    print listfile
 
263
    print
 
264
 
 
265
    return 0
 
266
 
 
267
def build_list_py_files(dir):
 
268
    """Builds a list of all py files found in a directory and its
 
269
    subdirectories. Returns this as a list of strings."""
 
270
    pylist = []
 
271
    for (dirpath, dirnames, filenames) in os.walk(dir):
 
272
        # Exclude directories beginning with a '.' (such as '.svn')
 
273
        filter_mutate(lambda x: x[0] != '.', dirnames)
 
274
        # All *.py files are added to the list
 
275
        pylist += [os.path.join(dirpath, item) for item in filenames
 
276
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
277
    return pylist
 
278
 
 
279
def writelist_pretty(file, list):
 
280
    """Writes a list one element per line, to a file."""
 
281
    if list == []:
 
282
        file.write("[]\n")
 
283
    else:
 
284
        file.write('[\n')
 
285
        for elem in list:
 
286
            file.write('    %s,\n' % repr(elem))
 
287
        file.write(']\n')
 
288
 
 
289
def conf(args):
 
290
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
291
    # Set up some variables
 
292
 
 
293
    cwd = os.getcwd()
 
294
    # the files that will be created/overwritten
 
295
    conffile = os.path.join(cwd, "www/conf/conf.py")
 
296
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
 
297
 
 
298
    # Fixed config options that we don't ask the admin
 
299
    default_app = "dummy"
 
300
 
 
301
    # Get command-line arguments to avoid asking questions.
 
302
 
 
303
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
 
304
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
 
305
 
 
306
    if args != []:
 
307
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
 
308
        return 2
 
309
 
 
310
    if opts == []:
 
311
        # Interactive mode. Prompt the user for all the values.
 
312
 
 
313
        print """This tool will create the following files:
 
314
    %s
 
315
    %s
 
316
prompting you for details about your configuration. The file will be
 
317
overwritten if it already exists. It will *not* install or deploy IVLE.
 
318
 
 
319
Please hit Ctrl+C now if you do not wish to do this.
 
320
""" % (conffile, conf_hfile)
 
321
 
 
322
        # Get information from the administrator
 
323
        # If EOF is encountered at any time during the questioning, just exit
 
324
        # silently
 
325
 
 
326
        root_dir = query_user(root_dir,
 
327
        """Root directory where IVLE is located (in URL space):""")
 
328
        ivle_install_dir = query_user(ivle_install_dir,
 
329
        'Root directory where IVLE will be installed (on the local file '
 
330
        'system):')
 
331
        jail_base = query_user(jail_base,
 
332
        """Root directory where the jails (containing user files) are stored
 
333
(on the local file system):""")
 
334
        allowed_uids = query_user(allowed_uids,
 
335
        """UID of the web server process which will run IVLE.
 
336
Only this user may execute the trampoline. May specify multiple users as
 
337
a comma-separated list.
 
338
    (eg. "1002,78")""")
 
339
 
 
340
    else:
 
341
        opts = dict(opts)
 
342
        # Non-interactive mode. Parse the options.
 
343
        if '--root_dir' in opts:
 
344
            root_dir = opts['--root_dir']
 
345
        if '--ivle_install_dir' in opts:
 
346
            ivle_install_dir = opts['--ivle_install_dir']
 
347
        if '--jail_base' in opts:
 
348
            jail_base = opts['--jail_base']
 
349
        if '--allowed_uids' in opts:
 
350
            allowed_uids = opts['--allowed_uids']
 
351
 
 
352
    # Error handling on input values
 
353
    try:
 
354
        allowed_uids = map(int, allowed_uids.split(','))
 
355
    except ValueError:
 
356
        print >>sys.stderr, (
 
357
        "Invalid UID list (%s).\n"
 
358
        "Must be a comma-separated list of integers." % allowed_uids)
 
359
        return 1
 
360
 
 
361
    # Write www/conf/conf.py
 
362
 
 
363
    try:
 
364
        conf = open(conffile, "w")
 
365
 
 
366
        conf.write("""# IVLE Configuration File
 
367
# conf.py
 
368
# Miscellaneous application settings
 
369
 
 
370
 
 
371
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
 
372
# with this).
 
373
# eg. "/" or "/ivle".
 
374
root_dir = "%s"
 
375
 
 
376
# In the local file system, where IVLE is actually installed.
 
377
# This directory should contain the "www" and "bin" directories.
 
378
ivle_install_dir = "%s"
 
379
 
 
380
# In the local file system, where are the student/user file spaces located.
 
381
# The user jails are expected to be located immediately in subdirectories of
 
382
# this location.
 
383
jail_base = "%s"
 
384
 
 
385
# Which application to load by default (if the user navigates to the top level
 
386
# of the site). This is the app's URL name.
 
387
# Note that if this app requires authentication, the user will first be
 
388
# presented with the login screen.
 
389
default_app = "%s"
 
390
""" % (root_dir, ivle_install_dir, jail_base, default_app))
 
391
 
 
392
        conf.close()
 
393
    except IOError, (errno, strerror):
 
394
        print "IO error(%s): %s" % (errno, strerror)
 
395
        sys.exit(1)
 
396
 
 
397
    print "Successfully wrote www/conf/conf.py"
 
398
 
 
399
    # Write trampoline/conf.h
 
400
 
 
401
    try:
 
402
        conf = open(conf_hfile, "w")
 
403
 
 
404
        conf.write("""/* IVLE Configuration File
 
405
 * conf.h
 
406
 * Administrator settings required by trampoline.
 
407
 * Note: trampoline will have to be rebuilt in order for changes to this file
 
408
 * to take effect.
 
409
 */
 
410
 
 
411
/* In the local file system, where are the jails located.
 
412
 * The trampoline does not allow the creation of a jail anywhere besides
 
413
 * jail_base or a subdirectory of jail_base.
 
414
 */
 
415
static const char* jail_base = "%s";
 
416
 
 
417
/* Which user IDs are allowed to run the trampoline.
 
418
 * This list should be limited to the web server user.
 
419
 * (Note that root is an implicit member of this list).
 
420
 */
 
421
static const int allowed_uids[] = { %s };
 
422
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
423
 
 
424
        conf.close()
 
425
    except IOError, (errno, strerror):
 
426
        print "IO error(%s): %s" % (errno, strerror)
 
427
        sys.exit(1)
 
428
 
 
429
    print "Successfully wrote trampoline/conf.h"
 
430
 
 
431
    print
 
432
    print "You may modify the configuration at any time by editing"
 
433
    print conffile
 
434
    print conf_hfile
 
435
    print
 
436
    return 0
 
437
 
 
438
def build(args):
 
439
    # Get "dry" variable from command line
 
440
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
441
    opts = dict(opts)
 
442
    dry = '-n' in opts or '--dry' in opts
 
443
 
 
444
    if dry:
 
445
        print "Dry run (no actions will be executed\n"
 
446
 
 
447
    # Compile the trampoline
 
448
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
 
449
        'trampoline/trampoline.c'], dry)
 
450
 
 
451
    # Create the jail and its subdirectories
 
452
    # Note: Other subdirs will be made by copying files
 
453
    action_mkdir('jail', dry)
 
454
    action_mkdir('jail/home', dry)
 
455
    action_mkdir('jail/tmp', dry)
 
456
 
 
457
    # Copy all console and operating system files into the jail
 
458
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
 
459
    copy_os_files_jail(dry)
 
460
 
 
461
    # Compile .py files into .pyc or .pyo files
 
462
    compileall.compile_dir('www', quiet=True)
 
463
    compileall.compile_dir('console', quiet=True)
 
464
 
 
465
    return 0
 
466
 
 
467
def copy_os_files_jail(dry):
 
468
    """Copies necessary Operating System files from their usual locations
 
469
    into the jail/ directory of the cwd."""
 
470
    # Currently source paths are configured for Ubuntu.
 
471
    copy_file_to_jail('/lib/ld-linux.so.2', dry)
 
472
    copy_file_to_jail('/lib/tls/i686/cmov/libc.so.6', dry)
 
473
    copy_file_to_jail('/lib/tls/i686/cmov/libdl.so.2', dry)
 
474
    copy_file_to_jail('/lib/tls/i686/cmov/libm.so.6', dry)
 
475
    copy_file_to_jail('/lib/tls/i686/cmov/libpthread.so.0', dry)
 
476
    copy_file_to_jail('/lib/tls/i686/cmov/libutil.so.1', dry)
 
477
    copy_file_to_jail('/usr/bin/python2.5', dry)
 
478
    # TODO: ln -s jail/usr/bin/python2.5 jail/usr/bin/python
 
479
    action_copytree('/usr/lib/python2.5', 'jail/usr/lib/python2.5', dry)
 
480
 
 
481
def copy_file_to_jail(src, dry):
 
482
    """Copies a single file from an absolute location into the same location
 
483
    within the jail. src must begin with a '/'. The jail will be located
 
484
    in a 'jail' subdirectory of the current path."""
 
485
    action_copyfile(src, 'jail' + src, dry)
 
486
 
 
487
def install(args):
 
488
    # Get "dry" and "nojail" variables from command line
 
489
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
 
490
    opts = dict(opts)
 
491
    dry = '-n' in opts or '--dry' in opts
 
492
    nojail = '--nojail' in opts
 
493
 
 
494
    if dry:
 
495
        print "Dry run (no actions will be executed\n"
 
496
 
 
497
    if not dry and os.geteuid() != 0:
 
498
        print >>sys.stderr, "Must be root to run install"
 
499
        print >>sys.stderr, "(I need to chown some files)."
 
500
        return 1
 
501
 
 
502
    # Create the target (install) directory
 
503
    action_mkdir(ivle_install_dir, dry)
 
504
 
 
505
    # Create bin and copy the compiled files there
 
506
    action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
 
507
    tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
 
508
    action_copyfile('trampoline/trampoline', tramppath, dry)
 
509
    # chown trampoline to root and set setuid bit
 
510
    action_chown_setuid(tramppath, dry)
 
511
 
 
512
    # Copy the www directory using the list
 
513
    action_copylist(install_list.list_www, ivle_install_dir, dry)
 
514
 
 
515
    if not nojail:
 
516
        # Copy the local jail directory built by the build action
 
517
        # to the jails template directory (it will be used as a template
 
518
        # for all the students' jails).
 
519
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
520
        # Set up symlinks inside the jail
 
521
        action_symlink(os.path.join(jail_base, 'template/usr/bin/python2.5'),
 
522
            os.path.join(jail_base, 'template/usr/bin/python'), dry)
 
523
 
 
524
    return 0
 
525
 
 
526
# The actions call Python os functions but print actions and handle dryness.
 
527
# May still throw os exceptions if errors occur.
 
528
 
 
529
class RunError:
 
530
    """Represents an error when running a program (nonzero return)."""
 
531
    def __init__(self, prog, retcode):
 
532
        self.prog = prog
 
533
        self.retcode = retcode
 
534
    def __str__(self):
 
535
        return str(self.prog) + " returned " + repr(self.retcode)
 
536
 
 
537
def action_runprog(prog, args, dry):
 
538
    """Runs a unix program. Searches in $PATH. Synchronous (waits for the
 
539
    program to return). Runs in the current environment. First prints the
 
540
    action as a "bash" line.
 
541
 
 
542
    Throws a RunError with a retcode of the return value of the program,
 
543
    if the program did not return 0.
 
544
 
 
545
    prog: String. Name of the program. (No path required, if in $PATH).
 
546
    args: [String]. Arguments to the program.
 
547
    dry: Bool. If True, prints but does not execute.
 
548
    """
 
549
    print prog, string.join(args, ' ')
 
550
    if dry: return
 
551
    ret = os.spawnvp(os.P_WAIT, prog, args)
 
552
    if ret != 0:
 
553
        raise RunError(prog, ret)
 
554
 
 
555
def action_mkdir(path, dry):
 
556
    """Calls mkdir. Silently ignored if the directory already exists.
 
557
    Creates all parent directories as necessary."""
 
558
    print "mkdir -p", path
 
559
    if dry: return
 
560
    try:
 
561
        os.makedirs(path)
 
562
    except OSError, (err, msg):
 
563
        if err != errno.EEXIST:
 
564
            raise
 
565
 
 
566
def action_copytree(src, dst, dry):
 
567
    """Copies an entire directory tree. Symlinks are seen as normal files and
 
568
    copies of the entire file (not the link) are made. Creates all parent
 
569
    directories as necessary.
 
570
 
 
571
    See shutil.copytree."""
 
572
    if os.access(dst, os.F_OK):
 
573
        print "rm -r", dst
 
574
        if not dry:
 
575
            shutil.rmtree(dst, True)
 
576
    print "cp -r", src, dst
 
577
    if dry: return
 
578
    shutil.copytree(src, dst)
 
579
 
 
580
def action_copylist(srclist, dst, dry):
 
581
    """Copies all files in a list to a new location. The files in the list
 
582
    are read relative to the current directory, and their destinations are the
 
583
    same paths relative to dst. Creates all parent directories as necessary.
 
584
    """
 
585
    for srcfile in srclist:
 
586
        dstfile = os.path.join(dst, srcfile)
 
587
        dstdir = os.path.split(dstfile)[0]
 
588
        if not os.path.isdir(dstdir):
 
589
            action_mkdir(dstdir, dry)
 
590
        print "cp -f", srcfile, dstfile
 
591
        if not dry:
 
592
            shutil.copyfile(srcfile, dstfile)
 
593
 
 
594
def action_copyfile(src, dst, dry):
 
595
    """Copies one file to a new location. Creates all parent directories
 
596
    as necessary.
 
597
    """
 
598
    dstdir = os.path.split(dst)[0]
 
599
    if not os.path.isdir(dstdir):
 
600
        action_mkdir(dstdir, dry)
 
601
    print "cp -f", src, dst
 
602
    if not dry:
 
603
        shutil.copyfile(src, dst)
 
604
 
 
605
def action_symlink(src, dst, dry):
 
606
    """Creates a symlink in a given location. Creates all parent directories
 
607
    as necessary.
 
608
    """
 
609
    dstdir = os.path.split(dst)[0]
 
610
    if not os.path.isdir(dstdir):
 
611
        action_mkdir(dstdir, dry)
 
612
    print "ln -s", src, dst
 
613
    if not dry:
 
614
        os.symlink(src, dst)
 
615
 
 
616
def action_chown_setuid(file, dry):
 
617
    """Chowns a file to root, and sets the setuid bit on the file.
 
618
    Calling this function requires the euid to be root.
 
619
    The actual mode of path is set to: rws--s--s
 
620
    """
 
621
    print "chown root:root", file
 
622
    if not dry:
 
623
        os.chown(file, 0, 0)
 
624
    print "chmod a+xs", file
 
625
    print "chmod u+rw", file
 
626
    if not dry:
 
627
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
 
628
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
 
629
 
 
630
def query_user(default, prompt):
 
631
    """Prompts the user for a string, which is read from a line of stdin.
 
632
    Exits silently if EOF is encountered. Returns the string, with spaces
 
633
    removed from the beginning and end.
 
634
 
 
635
    Returns default if a 0-length line (after spaces removed) was read.
 
636
    """
 
637
    sys.stdout.write('%s\n    (default: "%s")\n>' % (prompt, default))
 
638
    try:
 
639
        val = sys.stdin.readline()
 
640
    except KeyboardInterrupt:
 
641
        # Ctrl+C
 
642
        sys.stdout.write("\n")
 
643
        sys.exit(1)
 
644
    sys.stdout.write("\n")
 
645
    # If EOF, exit
 
646
    if val == '': sys.exit(1)
 
647
    # If empty line, return default
 
648
    val = val.strip()
 
649
    if val == '': return default
 
650
    return val
 
651
 
 
652
def filter_mutate(function, list):
 
653
    """Like built-in filter, but mutates the given list instead of returning a
 
654
    new one. Returns None."""
 
655
    i = len(list)-1
 
656
    while i >= 0:
 
657
        # Delete elements which do not match
 
658
        if not function(list[i]):
 
659
            del list[i]
 
660
        i -= 1
92
661
 
93
662
if __name__ == "__main__":
94
663
    sys.exit(main())
95