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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: William Grant
  • Date: 2009-06-24 10:20:19 UTC
  • Revision ID: grantw@unimelb.edu.au-20090624102019-v1s06kakgkookisa
Trim lots of useless HTTP status constants from ivle.dispatch.request.

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