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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: me at id
  • Date: 2009-01-15 01:18:00 UTC
  • mto: This revision was merged to the branch mainline in revision 1090.
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:branches%2Fstorm:1145
Give ivle.database.User {password,account}_expired attributes, and get
ivle.dispatch.login to use them.

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
# configure, build and install IVLE in three 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 config [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 lib/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
 
# Import modules from the website is tricky since they're in the www
72
 
# directory.
73
 
sys.path.append(os.path.join(os.getcwd(), 'lib'))
74
 
import conf
75
 
import common.makeuser
76
 
 
77
 
# Determine which Python version (2.4 or 2.5, for example) we are running,
78
 
# and use that as the filename to the Python directory.
79
 
# Just get the first 3 characters of sys.version.
80
 
PYTHON_VERSION = sys.version[0:3]
81
 
 
82
 
# Operating system files to copy over into the jail.
83
 
# These will be copied from the given place on the OS file system into the
84
 
# same place within the jail.
85
 
JAIL_FILES = [
86
 
    '/lib/ld-linux.so.2',
87
 
    '/lib/tls/i686/cmov/libc.so.6',
88
 
    '/lib/tls/i686/cmov/libdl.so.2',
89
 
    '/lib/tls/i686/cmov/libm.so.6',
90
 
    '/lib/tls/i686/cmov/libpthread.so.0',
91
 
    '/lib/tls/i686/cmov/libutil.so.1',
92
 
    '/etc/ld.so.conf',
93
 
    '/etc/ld.so.cache',
94
 
    # These 2 files do not exist in Ubuntu
95
 
    #'/etc/ld.so.preload',
96
 
    #'/etc/ld.so.nohwcap',
97
 
    # UNIX commands
98
 
    '/usr/bin/strace',
99
 
    '/bin/ls',
100
 
    '/bin/echo',
101
 
    # Needed by python
102
 
    '/usr/bin/python%s' % PYTHON_VERSION,
103
 
    # Needed by fileservice
104
 
    '/lib/libcom_err.so.2',
105
 
    '/lib/libcrypt.so.1',
106
 
    '/lib/libkeyutils.so.1',
107
 
    '/lib/libresolv.so.2',
108
 
    '/lib/librt.so.1',
109
 
    '/lib/libuuid.so.1',
110
 
    '/usr/lib/libapr-1.so.0',
111
 
    '/usr/lib/libaprutil-1.so.0',
112
 
    '/usr/lib/libdb-4.4.so',
113
 
    '/usr/lib/libexpat.so.1',
114
 
    '/usr/lib/libgcrypt.so.11',
115
 
    '/usr/lib/libgnutls.so.13',
116
 
    '/usr/lib/libgpg-error.so.0',
117
 
    '/usr/lib/libgssapi_krb5.so.2',
118
 
    '/usr/lib/libk5crypto.so.3',
119
 
    '/usr/lib/libkrb5.so.3',
120
 
    '/usr/lib/libkrb5support.so.0',
121
 
    '/usr/lib/liblber.so.2',
122
 
    '/usr/lib/libldap_r.so.2',
123
 
    '/usr/lib/libneon.so.26',
124
 
    '/usr/lib/libpq.so.5',
125
 
    '/usr/lib/libsasl2.so.2',
126
 
    '/usr/lib/libsqlite3.so.0',
127
 
    '/usr/lib/libsvn_client-1.so.1',
128
 
    '/usr/lib/libsvn_delta-1.so.1',
129
 
    '/usr/lib/libsvn_diff-1.so.1',
130
 
    '/usr/lib/libsvn_fs-1.so.1',
131
 
    '/usr/lib/libsvn_fs_base-1.so.1',
132
 
    '/usr/lib/libsvn_fs_fs-1.so.1',
133
 
    '/usr/lib/libsvn_ra-1.so.1',
134
 
    '/usr/lib/libsvn_ra_dav-1.so.1',
135
 
    '/usr/lib/libsvn_ra_local-1.so.1',
136
 
    '/usr/lib/libsvn_ra_svn-1.so.1',
137
 
    '/usr/lib/libsvn_repos-1.so.1',
138
 
    '/usr/lib/libsvn_subr-1.so.1',
139
 
    '/usr/lib/libsvn_wc-1.so.1',
140
 
    '/usr/lib/libtasn1.so.3',
141
 
    '/usr/lib/libxml2.so.2',
142
 
    # Needed by matplotlib
143
 
    '/usr/lib/i686/cmov/libssl.so.0.9.8',
144
 
    '/usr/lib/i686/cmov/libcrypto.so.0.9.8',
145
 
    '/lib/tls/i686/cmov/libnsl.so.1',
146
 
    '/usr/lib/libz.so.1',
147
 
    '/usr/lib/atlas/liblapack.so.3',
148
 
    '/usr/lib/atlas/libblas.so.3',
149
 
    '/usr/lib/libg2c.so.0',
150
 
    '/usr/lib/libstdc++.so.6',
151
 
    '/usr/lib/libfreetype.so.6',
152
 
    '/usr/lib/libpng12.so.0',
153
 
    '/usr/lib/libBLT.2.4.so.8.4',
154
 
    '/usr/lib/libtk8.4.so.0',
155
 
    '/usr/lib/libtcl8.4.so.0',
156
 
    '/usr/lib/tcl8.4/init.tcl',
157
 
    '/usr/lib/libX11.so.6',
158
 
    '/usr/lib/libXau.so.6',
159
 
    '/usr/lib/libXdmcp.so.6',
160
 
    '/lib/libgcc_s.so.1',
161
 
    '/etc/matplotlibrc',
162
 
]
163
 
# Symlinks to make within the jail. Src mapped to dst.
164
 
JAIL_LINKS = {
165
 
    'python%s' % PYTHON_VERSION: 'jail/usr/bin/python',
166
 
}
167
 
# Trees to copy. Src mapped to dst (these will be passed to action_copytree).
168
 
JAIL_COPYTREES = {
169
 
    '/usr/lib/python%s' % PYTHON_VERSION:
170
 
        'jail/usr/lib/python%s' % PYTHON_VERSION,
171
 
    '/usr/share/matplotlib': 'jail/usr/share/matplotlib',
172
 
    '/etc/ld.so.conf.d': 'jail/etc/ld.so.conf.d',
173
 
}
174
 
 
175
 
class ConfigOption:
176
 
    """A configuration option; one of the things written to conf.py."""
177
 
    def __init__(self, option_name, default, prompt, comment):
178
 
        """Creates a configuration option.
179
 
        option_name: Name of the variable in conf.py. Also name of the
180
 
            command-line argument to setup.py conf.
181
 
        default: Default value for this variable.
182
 
        prompt: (Short) string presented during the interactive prompt in
183
 
            setup.py conf.
184
 
        comment: (Long) comment string stored in conf.py. Each line of this
185
 
            string should begin with a '#'.
186
 
        """
187
 
        self.option_name = option_name
188
 
        self.default = default
189
 
        self.prompt = prompt
190
 
        self.comment = comment
191
 
 
192
 
# Configuration options, defaults and descriptions
193
 
config_options = []
194
 
config_options.append(ConfigOption("root_dir", "/",
195
 
    """Root directory where IVLE is located (in URL space):""",
196
 
    """
197
 
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
198
 
# with this).
199
 
# eg. "/" or "/ivle"."""))
200
 
config_options.append(ConfigOption("ivle_install_dir", "/opt/ivle",
201
 
    'Root directory where IVLE will be installed (on the local file '
202
 
    'system):',
203
 
    """
204
 
# In the local file system, where IVLE is actually installed.
205
 
# This directory should contain the "www" and "bin" directories."""))
206
 
config_options.append(ConfigOption("jail_base", "/home/informatics/jails",
207
 
    """Root directory where the jails (containing user files) are stored
208
 
(on the local file system):""",
209
 
    """
210
 
# In the local file system, where are the student/user file spaces located.
211
 
# The user jails are expected to be located immediately in subdirectories of
212
 
# this location."""))
213
 
config_options.append(ConfigOption("subjects_base",
214
 
    "/home/informatics/subjects",
215
 
    """Root directory where the subject directories (containing worksheets
216
 
and other per-subject files) are stored (on the local file system):""",
217
 
    """
218
 
# In the local file system, where are the per-subject file spaces located.
219
 
# The individual subject directories are expected to be located immediately
220
 
# in subdirectories of this location."""))
221
 
config_options.append(ConfigOption("exercises_base",
222
 
    "/home/informatics/exercises",
223
 
    """Root directory where the exercise directories (containing
224
 
subject-independent exercise sheets) are stored (on the local file
225
 
system):""",
226
 
    """
227
 
# In the local file system, where are the subject-independent exercise sheet
228
 
# file spaces located."""))
229
 
config_options.append(ConfigOption("public_host", "public.localhost",
230
 
    """Hostname which will cause the server to go into "public mode",
231
 
providing login-free access to student's published work:""",
232
 
    """
233
 
# The server goes into "public mode" if the browser sends a request with this
234
 
# host. This is for security reasons - we only serve public student files on a
235
 
# separate domain to the main IVLE site.
236
 
# Public mode does not use cookies, and serves only public content.
237
 
# Private mode (normal mode) requires login, and only serves files relevant to
238
 
# the logged-in user."""))
239
 
config_options.append(ConfigOption("allowed_uids", "33",
240
 
    """UID of the web server process which will run IVLE.
241
 
Only this user may execute the trampoline. May specify multiple users as
242
 
a comma-separated list.
243
 
    (eg. "1002,78")""",
244
 
    """
245
 
# The User-ID of the web server process which will run IVLE, and any other
246
 
# users who are allowed to run the trampoline. This is stores as a string of
247
 
# comma-separated integers, simply because it is not used within Python, only
248
 
# used by the setup program to write to conf.h (see setup.py config)."""))
249
 
config_options.append(ConfigOption("db_host", "localhost",
250
 
    """PostgreSQL Database config
251
 
==========================
252
 
Hostname of the DB server:""",
253
 
    """
254
 
### PostgreSQL Database config ###
255
 
# Database server hostname"""))
256
 
config_options.append(ConfigOption("db_port", "5432",
257
 
    """Port of the DB server:""",
258
 
    """
259
 
# Database server port"""))
260
 
config_options.append(ConfigOption("db_dbname", "ivle",
261
 
    """Database name:""",
262
 
    """
263
 
# Database name"""))
264
 
config_options.append(ConfigOption("db_user", "postgres",
265
 
    """Username for DB server login:""",
266
 
    """
267
 
# Database username"""))
268
 
config_options.append(ConfigOption("db_password", "",
269
 
    """Password for DB server login:
270
 
    (Caution: This password is stored in plaintext in lib/conf/conf.py)""",
271
 
    """
272
 
# Database password"""))
273
 
config_options.append(ConfigOption("ldap_url", "ldaps://www.example.com",
274
 
    """URL for LDAP authentication server:""",
275
 
    """
276
 
# URL for LDAP authentication server"""))
277
 
config_options.append(ConfigOption("ldap_format_string",
278
 
    "uid=%s,ou=users,o=example",
279
 
    """Format string for LDAP auth request:
280
 
    (Must contain a single "%s" for the user's login name)""",
281
 
    """
282
 
# Format string for LDAP auth request
283
 
# (Must contain a single "%s" for the user's login name)"""))
284
 
config_options.append(ConfigOption("svn_addr", "http://svn.localhost/",
285
 
    """The base url for accessing subversion repositories:""",
286
 
    """
287
 
# The base url for accessing subversion repositories."""))
288
 
config_options.append(ConfigOption("svn_conf", "/opt/ivle/svn/svn.conf",
289
 
    """The location of the subversion configuration file used by apache
290
 
to host the user repositories:""",
291
 
    """
292
 
# The location of the subversion configuration file used by
293
 
# apache to host the user repositories."""))
294
 
config_options.append(ConfigOption("svn_repo_path", "/home/informatics/repositories",
295
 
    """The root directory for the subversion repositories:""",
296
 
    """
297
 
# The root directory for the subversion repositories."""))
298
 
config_options.append(ConfigOption("svn_auth_ivle", "/opt/ivle/svn/ivle.auth",
299
 
    """The location of the password file used to authenticate users
300
 
of the subversion repository from the ivle server:""",
301
 
    """
302
 
# The location of the password file used to authenticate users
303
 
# of the subversion repository from the ivle server."""))
304
 
config_options.append(ConfigOption("svn_auth_local", "/opt/ivle/svn/local.auth",
305
 
    """The location of the password file used to authenticate local users
306
 
of the subversion repository:""",
307
 
    """
308
 
# The location of the password file used to authenticate local users
309
 
# of the subversion repository."""))
310
 
config_options.append(ConfigOption("usrmgt_host", "localhost",
311
 
    """The hostname where the usrmgt-server runs:""",
312
 
    """
313
 
# The hostname where the usrmgt-server runs."""))
314
 
config_options.append(ConfigOption("usrmgt_port", "2178",
315
 
    """The port where the usrmgt-server runs:""",
316
 
    """
317
 
# The port where the usrmgt-server runs."""))
318
 
config_options.append(ConfigOption("usrmgt_magic", "",
319
 
    """The password for the usrmgt-server:""",
320
 
    """
321
 
# The password for the usrmgt-server."""))
322
 
 
323
 
# Try importing existing conf, but if we can't just set up defaults
324
 
# The reason for this is that these settings are used by other phases
325
 
# of setup besides conf, so we need to know them.
326
 
# Also this allows you to hit Return to accept the existing value.
327
 
try:
328
 
    confmodule = __import__("lib/conf/conf")
329
 
    for opt in config_options:
330
 
        try:
331
 
            globals()[opt.option_name] = confmodule.__dict__[opt.option_name]
332
 
        except:
333
 
            globals()[opt.option_name] = opt.default
334
 
except ImportError:
335
 
    # Just set reasonable defaults
336
 
    for opt in config_options:
337
 
        globals()[opt.option_name] = opt.default
338
 
 
339
 
# Try importing install_list, but don't fail if we can't, because listmake can
340
 
# function without it.
341
 
try:
342
 
    import install_list
343
 
except:
344
 
    pass
345
 
 
346
 
# Mime types which will automatically be placed in the list by listmake.
347
 
# Note that listmake is not intended to be run by the final user (the system
348
 
# administrator who installs this), so the developers can customize the list
349
 
# as necessary, and include it in the distribution.
350
 
listmake_mimetypes = ['text/x-python', 'text/html',
351
 
    'application/x-javascript', 'application/javascript',
352
 
    'text/css', 'image/png', 'image/gif', 'image/jpeg', 'application/xml', 
353
 
    'application/x-httpd-php']
354
 
 
355
 
# Main function skeleton from Guido van Rossum
356
 
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
 
30
import setup.configure
 
31
import setup.build
 
32
import setup.install
 
33
 
357
34
 
358
35
def main(argv=None):
359
36
    if argv is None:
362
39
    # Print the opening spiel including the GPL notice
363
40
 
364
41
    print """IVLE - Informatics Virtual Learning Environment Setup
365
 
Copyright (C) 2007-2008 The University of Melbourne
 
42
Copyright (C) 2007-2009 The University of Melbourne
366
43
IVLE comes with ABSOLUTELY NO WARRANTY.
367
44
This is free software, and you are welcome to redistribute it
368
45
under certain conditions. See LICENSE.txt for details.
378
55
        help([])
379
56
        return 1
380
57
 
381
 
    # Disallow run as root unless installing
382
 
    if (operation != 'install' and operation != 'updatejails'
383
 
        and os.geteuid() == 0):
384
 
        print >>sys.stderr, "I do not want to run this stage as root."
385
 
        print >>sys.stderr, "Please run as a normal user."
386
 
        return 1
 
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):
387
77
    # Call the requested operation's function
388
78
    try:
389
79
        oper_func = {
390
80
            'help' : help,
391
 
            'config' : conf,
392
 
            'build' : build,
393
 
            'listmake' : listmake,
394
 
            'install' : install,
395
 
            'updatejails' : updatejails,
 
81
            'config' : setup.configure.configure,
 
82
            'build' : setup.build.build,
 
83
            'install' : setup.install.install,
 
84
            #'updatejails' : None,
396
85
        }[operation]
397
86
    except KeyError:
398
87
        print >>sys.stderr, (
399
88
            """Invalid operation '%s'. Try python setup.py help."""
400
89
            % operation)
401
 
        return 1
402
 
    return oper_func(argv[2:])
403
 
 
404
 
# Operation functions
405
 
 
406
 
def help(args):
407
 
    if args == []:
408
 
        print """Usage: python setup.py operation [args]
409
 
Operation (and args) can be:
410
 
    help [operation]
411
 
    listmake (developer use only)
412
 
    config [args]
413
 
    build
414
 
    install [--nojail] [--nosubjects] [-n|--dry]
415
 
"""
416
 
        return 1
417
 
    elif len(args) != 1:
418
 
        print """Usage: python setup.py help [operation]"""
419
 
        return 2
420
 
    else:
421
 
        operation = args[0]
422
 
 
423
 
    if operation == 'help':
424
 
        print """python setup.py help [operation]
425
 
Prints the usage message or detailed help on an operation, then exits."""
426
 
    elif operation == 'listmake':
427
 
        print """python setup.py listmake
428
 
(For developer use only)
429
 
Recurses through the source tree and builds a list of all files which should
430
 
be copied upon installation. This should be run by the developer before
431
 
cutting a distribution, and the listfile it generates should be included in
432
 
the distribution, avoiding the administrator having to run it."""
433
 
    elif operation == 'config':
434
 
        print """python setup.py config [args]
435
 
Configures IVLE with machine-specific details, most notably, various paths.
436
 
Either prompts the administrator for these details or accepts them as
437
 
command-line args. Will be interactive only if there are no arguments given.
438
 
Takes defaults from existing conf file if it exists.
439
 
 
440
 
To run IVLE out of the source directory (allowing development without having
441
 
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
442
 
directory, and run build/install one time.
443
 
 
444
 
Creates lib/conf/conf.py and trampoline/conf.h.
445
 
 
446
 
Args are:"""
447
 
        for opt in config_options:
448
 
            print "    --" + opt.option_name
449
 
        print """As explained in the interactive prompt or conf.py.
450
 
"""
451
 
    elif operation == 'build':
452
 
        print """python -O setup.py build [--dry|-n]
453
 
Compiles all files and sets up a jail template in the source directory.
454
 
-O is recommended to cause compilation to be optimised.
455
 
Details:
456
 
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
457
 
Creates jail/.
458
 
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
459
 
Copies console/ to a location within the jail.
460
 
Copies OS programs and files to corresponding locations within the jail
461
 
  (eg. python and Python libs, ld.so, etc).
462
 
Generates .pyc or .pyo files for all the IVLE .py files.
463
 
 
464
 
--dry | -n  Print out the actions but don't do anything."""
465
 
    elif operation == 'install':
466
 
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
467
 
(Requires root)
468
 
Create target install directory ($target).
469
 
Create $target/bin.
470
 
Copy trampoline/trampoline to $target/bin.
471
 
chown and chmod the installed trampoline.
472
 
Copy www/ to $target.
473
 
Copy jail/ to jails template directory (unless --nojail specified).
474
 
Copy subjects/ to subjects directory (unless --nosubjects specified).
475
 
 
476
 
--nojail        Do not copy the jail.
477
 
--nosubjects    Do not copy the subjects and exercises directories.
478
 
--dry | -n  Print out the actions but don't do anything."""
479
 
    elif operation == 'updatejails':
480
 
        print """sudo python setup.py updatejails [--dry|-n]
481
 
(Requires root)
482
 
Copy jail/ to each subdirectory in jails directory.
483
 
 
484
 
--dry | -n  Print out the actions but don't do anything."""
485
 
    else:
486
 
        print >>sys.stderr, (
487
 
            """Invalid operation '%s'. Try python setup.py help."""
488
 
            % operation)
489
 
    return 1
490
 
 
491
 
def listmake(args):
492
 
    # We build two separate lists, by walking www and console
493
 
    list_www = build_list_py_files('www')
494
 
    list_lib = build_list_py_files('lib')
495
 
    list_subjects = build_list_py_files('subjects', no_top_level=True)
496
 
    list_exercises = build_list_py_files('exercises', no_top_level=True)
497
 
    list_scripts = [
498
 
        "scripts/python-console",
499
 
        "scripts/fileservice",
500
 
        "scripts/usrmgt-server",
501
 
    ]
502
 
    # Make sure that the files generated by conf are in the list
503
 
    # (since listmake is typically run before conf)
504
 
    if "lib/conf/conf.py" not in list_lib:
505
 
        list_lib.append("lib/conf/conf.py")
506
 
    # Write these out to a file
507
 
    cwd = os.getcwd()
508
 
    # the files that will be created/overwritten
509
 
    listfile = os.path.join(cwd, "install_list.py")
510
 
 
511
 
    try:
512
 
        file = open(listfile, "w")
513
 
 
514
 
        file.write("""# IVLE Configuration File
515
 
# install_list.py
516
 
# Provides lists of all files to be installed by `setup.py install' from
517
 
# certain directories.
518
 
# Note that any files with the given filename plus 'c' or 'o' (that is,
519
 
# compiled .pyc or .pyo files) will be copied as well.
520
 
 
521
 
# List of all installable files in www directory.
522
 
list_www = """)
523
 
        writelist_pretty(file, list_www)
524
 
        file.write("""
525
 
# List of all installable files in lib directory.
526
 
list_lib = """)
527
 
        writelist_pretty(file, list_lib)
528
 
        file.write("""
529
 
# List of all installable files in scripts directory.
530
 
list_scripts = """)
531
 
        writelist_pretty(file, list_scripts)
532
 
        file.write("""
533
 
# List of all installable files in subjects directory.
534
 
# This is to install sample subjects and material.
535
 
list_subjects = """)
536
 
        writelist_pretty(file, list_subjects)
537
 
        file.write("""
538
 
# List of all installable files in exercises directory.
539
 
# This is to install sample exercise material.
540
 
list_exercises = """)
541
 
        writelist_pretty(file, list_exercises)
542
 
 
543
 
        file.close()
544
 
    except IOError, (errno, strerror):
545
 
        print "IO error(%s): %s" % (errno, strerror)
546
 
        sys.exit(1)
547
 
 
548
 
    print "Successfully wrote install_list.py"
549
 
 
550
 
    print
551
 
    print ("You may modify the set of installable files before cutting the "
552
 
            "distribution:")
553
 
    print listfile
554
 
    print
555
 
 
556
 
    return 0
557
 
 
558
 
def build_list_py_files(dir, no_top_level=False):
559
 
    """Builds a list of all py files found in a directory and its
560
 
    subdirectories. Returns this as a list of strings.
561
 
    no_top_level=True means the file paths will not include the top-level
562
 
    directory.
563
 
    """
564
 
    pylist = []
565
 
    for (dirpath, dirnames, filenames) in os.walk(dir):
566
 
        # Exclude directories beginning with a '.' (such as '.svn')
567
 
        filter_mutate(lambda x: x[0] != '.', dirnames)
568
 
        # All *.py files are added to the list
569
 
        pylist += [os.path.join(dirpath, item) for item in filenames
570
 
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
571
 
    if no_top_level:
572
 
        for i in range(0, len(pylist)):
573
 
            _, pylist[i] = pylist[i].split(os.sep, 1)
574
 
    return pylist
575
 
 
576
 
def writelist_pretty(file, list):
577
 
    """Writes a list one element per line, to a file."""
578
 
    if list == []:
579
 
        file.write("[]\n")
580
 
    else:
581
 
        file.write('[\n')
582
 
        for elem in list:
583
 
            file.write('    %s,\n' % repr(elem))
584
 
        file.write(']\n')
585
 
 
586
 
def conf(args):
587
 
    global db_port, usrmgt_port
588
 
    # Set up some variables
589
 
 
590
 
    cwd = os.getcwd()
591
 
    # the files that will be created/overwritten
592
 
    conffile = os.path.join(cwd, "lib/conf/conf.py")
593
 
    jailconffile = os.path.join(cwd, "lib/conf/jailconf.py")
594
 
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
595
 
 
596
 
    # Get command-line arguments to avoid asking questions.
597
 
 
598
 
    optnames = []
599
 
    for opt in config_options:
600
 
        optnames.append(opt.option_name + "=")
601
 
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
602
 
 
603
 
    if args != []:
604
 
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
605
 
        return 2
606
 
 
607
 
    if opts == []:
608
 
        # Interactive mode. Prompt the user for all the values.
609
 
 
610
 
        print """This tool will create the following files:
611
 
    %s
612
 
    %s
613
 
    %s
614
 
prompting you for details about your configuration. The file will be
615
 
overwritten if it already exists. It will *not* install or deploy IVLE.
616
 
 
617
 
Please hit Ctrl+C now if you do not wish to do this.
618
 
""" % (conffile, jailconffile, conf_hfile)
619
 
 
620
 
        # Get information from the administrator
621
 
        # If EOF is encountered at any time during the questioning, just exit
622
 
        # silently
623
 
 
624
 
        for opt in config_options:
625
 
            globals()[opt.option_name] = \
626
 
                query_user(globals()[opt.option_name], opt.prompt)
627
 
    else:
628
 
        opts = dict(opts)
629
 
        # Non-interactive mode. Parse the options.
630
 
        for opt in config_options:
631
 
            if '--' + opt.option_name in opts:
632
 
                globals()[opt.option_name] = opts['--' + opt.option_name]
633
 
 
634
 
    # Error handling on input values
635
 
    try:
636
 
        allowed_uids_list = map(int, allowed_uids.split(','))
637
 
    except ValueError:
638
 
        print >>sys.stderr, (
639
 
        "Invalid UID list (%s).\n"
640
 
        "Must be a comma-separated list of integers." % allowed_uids)
641
 
        return 1
642
 
    try:
643
 
        db_port = int(db_port)
644
 
        if db_port < 0 or db_port >= 65536: raise ValueError()
645
 
    except ValueError:
646
 
        print >>sys.stderr, (
647
 
        "Invalid DB port (%s).\n"
648
 
        "Must be an integer between 0 and 65535." % repr(db_port))
649
 
        return 1
650
 
    try:
651
 
        usrmgt_port = int(usrmgt_port)
652
 
        if usrmgt_port < 0 or usrmgt_port >= 65536: raise ValueError()
653
 
    except ValueError:
654
 
        print >>sys.stderr, (
655
 
        "Invalid user management port (%s).\n"
656
 
        "Must be an integer between 0 and 65535." % repr(usrmgt_port))
657
 
        return 1
658
 
 
659
 
    # Write lib/conf/conf.py
660
 
 
661
 
    try:
662
 
        conf = open(conffile, "w")
663
 
 
664
 
        conf.write("""# IVLE Configuration File
665
 
# conf.py
666
 
# Miscellaneous application settings
667
 
 
668
 
""")
669
 
        for opt in config_options:
670
 
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
671
 
                repr(globals()[opt.option_name])))
672
 
 
673
 
        conf.close()
674
 
    except IOError, (errno, strerror):
675
 
        print "IO error(%s): %s" % (errno, strerror)
676
 
        sys.exit(1)
677
 
 
678
 
    print "Successfully wrote lib/conf/conf.py"
679
 
 
680
 
    # Write conf/jailconf.py
681
 
 
682
 
    try:
683
 
        conf = open(jailconffile, "w")
684
 
 
685
 
        # In the "in-jail" version of conf, we don't need MOST of the details
686
 
        # (it would be a security risk to have them here).
687
 
        # So we just write root_dir, and jail_base is "/".
688
 
        # (jail_base being "/" means "jail-relative" paths are relative to "/"
689
 
        # when inside the jail.)
690
 
        conf.write("""# IVLE Configuration File
691
 
# conf.py
692
 
# Miscellaneous application settings
693
 
# (User jail version)
694
 
 
695
 
 
696
 
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
697
 
# with this).
698
 
# eg. "/" or "/ivle".
699
 
root_dir = %s
700
 
 
701
 
# In the local file system, where are the student/user file spaces located.
702
 
# The user jails are expected to be located immediately in subdirectories of
703
 
# this location.
704
 
jail_base = '/'
705
 
 
706
 
# The hostname for serving publicly accessible pages
707
 
public_host = %s
708
 
""" % (repr(root_dir),repr(public_host)))
709
 
 
710
 
        conf.close()
711
 
    except IOError, (errno, strerror):
712
 
        print "IO error(%s): %s" % (errno, strerror)
713
 
        sys.exit(1)
714
 
 
715
 
    print "Successfully wrote lib/conf/jailconf.py"
716
 
 
717
 
    # Write trampoline/conf.h
718
 
 
719
 
    try:
720
 
        conf = open(conf_hfile, "w")
721
 
 
722
 
        conf.write("""/* IVLE Configuration File
723
 
 * conf.h
724
 
 * Administrator settings required by trampoline.
725
 
 * Note: trampoline will have to be rebuilt in order for changes to this file
726
 
 * to take effect.
727
 
 */
728
 
 
729
 
/* In the local file system, where are the jails located.
730
 
 * The trampoline does not allow the creation of a jail anywhere besides
731
 
 * jail_base or a subdirectory of jail_base.
732
 
 */
733
 
static const char* jail_base = "%s";
734
 
 
735
 
/* Which user IDs are allowed to run the trampoline.
736
 
 * This list should be limited to the web server user.
737
 
 * (Note that root is an implicit member of this list).
738
 
 */
739
 
static const int allowed_uids[] = { %s };
740
 
""" % (repr(jail_base)[1:-1], repr(allowed_uids_list)[1:-1]))
741
 
    # Note: The above uses PYTHON reprs, not C reprs
742
 
    # However they should be the same with the exception of the outer
743
 
    # characters, which are stripped off and replaced
744
 
 
745
 
        conf.close()
746
 
    except IOError, (errno, strerror):
747
 
        print "IO error(%s): %s" % (errno, strerror)
748
 
        sys.exit(1)
749
 
 
750
 
    print "Successfully wrote trampoline/conf.h"
751
 
 
752
 
    print
753
 
    print "You may modify the configuration at any time by editing"
754
 
    print conffile
755
 
    print jailconffile
756
 
    print conf_hfile
757
 
    print
758
 
    return 0
759
 
 
760
 
def build(args):
761
 
    # Get "dry" variable from command line
762
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
763
 
    opts = dict(opts)
764
 
    dry = '-n' in opts or '--dry' in opts
765
 
 
766
 
    if dry:
767
 
        print "Dry run (no actions will be executed\n"
768
 
 
769
 
    # Compile the trampoline
770
 
    curdir = os.getcwd()
771
 
    os.chdir('trampoline')
772
 
    action_runprog('make', [], dry)
773
 
    os.chdir(curdir)
774
 
 
775
 
    # Create the jail and its subdirectories
776
 
    # Note: Other subdirs will be made by copying files
777
 
    action_mkdir('jail', dry)
778
 
    action_mkdir('jail/home', dry)
779
 
    action_mkdir('jail/tmp', dry)
780
 
 
781
 
    # Copy all console and operating system files into the jail
782
 
    action_copylist(install_list.list_scripts, 'jail/opt/ivle', dry)
783
 
    copy_os_files_jail(dry)
784
 
    # Chmod the python console
785
 
    action_chmod_x('jail/opt/ivle/scripts/python-console', dry)
786
 
    action_chmod_x('jail/opt/ivle/scripts/fileservice', dry)
787
 
    
788
 
    # Also copy the IVLE lib directory into the jail
789
 
    # This is necessary for running certain scripts
790
 
    action_copylist(install_list.list_lib, 'jail/opt/ivle', dry)
791
 
    # IMPORTANT: The file jail/opt/ivle/lib/conf/conf.py contains details
792
 
    # which could compromise security if left in the jail (such as the DB
793
 
    # password).
794
 
    # The "safe" version is in jailconf.py. Delete conf.py and replace it with
795
 
    # jailconf.py.
796
 
    action_copyfile('lib/conf/jailconf.py',
797
 
        'jail/opt/ivle/lib/conf/conf.py', dry)
798
 
 
799
 
    # Compile .py files into .pyc or .pyo files
800
 
    compileall.compile_dir('www', quiet=True)
801
 
    compileall.compile_dir('lib', quiet=True)
802
 
    compileall.compile_dir('scripts', quiet=True)
803
 
    compileall.compile_dir('jail/opt/ivle/lib', quiet=True)
804
 
 
805
 
    # Set up ivle.pth inside the jail
806
 
    # Need to set /opt/ivle/lib to be on the import path
807
 
    ivle_pth = \
808
 
        "jail/usr/lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION
809
 
    f = open(ivle_pth, 'w')
810
 
    f.write('/opt/ivle/lib\n')
811
 
    f.close()
812
 
 
813
 
    return 0
814
 
 
815
 
def copy_os_files_jail(dry):
816
 
    """Copies necessary Operating System files from their usual locations
817
 
    into the jail/ directory of the cwd."""
818
 
    # Currently source paths are configured for Ubuntu.
819
 
    for filename in JAIL_FILES:
820
 
        copy_file_to_jail(filename, dry)
821
 
    for src, dst in JAIL_LINKS.items():
822
 
        action_symlink(src, dst, dry)
823
 
    for src, dst in JAIL_COPYTREES.items():
824
 
        action_copytree(src, dst, dry)
825
 
 
826
 
def copy_file_to_jail(src, dry):
827
 
    """Copies a single file from an absolute location into the same location
828
 
    within the jail. src must begin with a '/'. The jail will be located
829
 
    in a 'jail' subdirectory of the current path."""
830
 
    action_copyfile(src, 'jail' + src, dry)
831
 
 
832
 
def install(args):
833
 
    # Get "dry" and "nojail" variables from command line
834
 
    (opts, args) = getopt.gnu_getopt(args, "n",
835
 
        ['dry', 'nojail', 'nosubjects'])
836
 
    opts = dict(opts)
837
 
    dry = '-n' in opts or '--dry' in opts
838
 
    nojail = '--nojail' in opts
839
 
    nosubjects = '--nosubjects' in opts
840
 
 
841
 
    if dry:
842
 
        print "Dry run (no actions will be executed\n"
843
 
 
844
 
    if not dry and os.geteuid() != 0:
845
 
        print >>sys.stderr, "Must be root to run install"
846
 
        print >>sys.stderr, "(I need to chown some files)."
847
 
        return 1
848
 
 
849
 
    # Create the target (install) directory
850
 
    action_mkdir(ivle_install_dir, dry)
851
 
 
852
 
    # Create bin and copy the compiled files there
853
 
    action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
854
 
    tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
855
 
    action_copyfile('trampoline/trampoline', tramppath, dry)
856
 
    # chown trampoline to root and set setuid bit
857
 
    action_chown_setuid(tramppath, dry)
858
 
 
859
 
    # Copy the www and lib directories using the list
860
 
    action_copylist(install_list.list_www, ivle_install_dir, dry)
861
 
    action_copylist(install_list.list_lib, ivle_install_dir, dry)
862
 
 
863
 
    if not nojail:
864
 
        # Copy the local jail directory built by the build action
865
 
        # to the jails template directory (it will be used as a template
866
 
        # for all the students' jails).
867
 
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
868
 
    if not nosubjects:
869
 
        # Copy the subjects and exercises directories across
870
 
        action_copylist(install_list.list_subjects, subjects_base, dry,
871
 
            srcdir="./subjects")
872
 
        action_copylist(install_list.list_exercises, exercises_base, dry,
873
 
            srcdir="./exercises")
874
 
 
875
 
    # Append IVLE path to ivle.pth in python site packages
876
 
    # (Unless it's already there)
877
 
    ivle_pth = os.path.join(sys.prefix,
878
 
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
879
 
    ivle_www = os.path.join(ivle_install_dir, "www")
880
 
    ivle_lib = os.path.join(ivle_install_dir, "lib")
881
 
    write_ivle_pth = True
882
 
    write_ivle_lib_pth = True
883
 
    try:
884
 
        file = open(ivle_pth, 'r')
885
 
        for line in file:
886
 
            if line.strip() == ivle_www:
887
 
                write_ivle_pth = False
888
 
            elif line.strip() == ivle_lib:
889
 
                write_ivle_lib_pth = False
890
 
        file.close()
891
 
    except (IOError, OSError):
892
 
        pass
893
 
    if write_ivle_pth:
894
 
        action_append(ivle_pth, ivle_www)
895
 
    if write_ivle_lib_pth:
896
 
        action_append(ivle_pth, ivle_lib)
897
 
 
898
 
    return 0
899
 
 
900
 
def updatejails(args):
901
 
    # Get "dry" variable from command line
902
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
903
 
    opts = dict(opts)
904
 
    dry = '-n' in opts or '--dry' in opts
905
 
 
906
 
    if dry:
907
 
        print "Dry run (no actions will be executed\n"
908
 
 
909
 
    if not dry and os.geteuid() != 0:
910
 
        print >>sys.stderr, "Must be root to run install"
911
 
        print >>sys.stderr, "(I need to chown some files)."
912
 
        return 1
913
 
 
914
 
    # Update the template jail directory in case it hasn't been installed
915
 
    # recently.
916
 
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
917
 
 
918
 
    # Re-link all the files in all students jails.
919
 
    for dir in os.listdir(jail_base):
920
 
        if dir == 'template': continue
921
 
        # First back up the student's home directory
922
 
        temp_home = os.tmpnam()
923
 
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
924
 
        # Delete the student's jail and relink the jail files
925
 
        action_linktree(os.path.join(jail_base, 'template'),
926
 
            os.path.join(jail_base, dir), dry)
927
 
        # Restore the student's home directory
928
 
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
929
 
        # Set up the user's home directory just in case they don't have a
930
 
        # directory for this yet
931
 
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
932
 
 
933
 
    return 0
934
 
 
935
 
# The actions call Python os functions but print actions and handle dryness.
936
 
# May still throw os exceptions if errors occur.
937
 
 
938
 
class RunError:
939
 
    """Represents an error when running a program (nonzero return)."""
940
 
    def __init__(self, prog, retcode):
941
 
        self.prog = prog
942
 
        self.retcode = retcode
943
 
    def __str__(self):
944
 
        return str(self.prog) + " returned " + repr(self.retcode)
945
 
 
946
 
def action_runprog(prog, args, dry):
947
 
    """Runs a unix program. Searches in $PATH. Synchronous (waits for the
948
 
    program to return). Runs in the current environment. First prints the
949
 
    action as a "bash" line.
950
 
 
951
 
    Throws a RunError with a retcode of the return value of the program,
952
 
    if the program did not return 0.
953
 
 
954
 
    prog: String. Name of the program. (No path required, if in $PATH).
955
 
    args: [String]. Arguments to the program.
956
 
    dry: Bool. If True, prints but does not execute.
957
 
    """
958
 
    print prog, string.join(args, ' ')
959
 
    if dry: return
960
 
    ret = os.spawnvp(os.P_WAIT, prog, args)
961
 
    if ret != 0:
962
 
        raise RunError(prog, ret)
963
 
 
964
 
def action_remove(path, dry):
965
 
    """Calls rmtree, deleting the target file if it exists."""
966
 
    try:
967
 
        print "rm -r", path
968
 
        if not dry:
969
 
            shutil.rmtree(path, True)
970
 
    except OSError, (err, msg):
971
 
        if err != errno.EEXIST:
972
 
            raise
973
 
        # Otherwise, didn't exist, so we don't care
974
 
 
975
 
def action_rename(src, dst, dry):
976
 
    """Calls rename. Deletes the target if it already exists."""
977
 
    action_remove(dst, dry)
978
 
    print "mv ", src, dst
979
 
    if dry: return
980
 
    try:
981
 
        os.rename(src, dst)
982
 
    except OSError, (err, msg):
983
 
        if err != errno.EEXIST:
984
 
            raise
985
 
 
986
 
def action_mkdir(path, dry):
987
 
    """Calls mkdir. Silently ignored if the directory already exists.
988
 
    Creates all parent directories as necessary."""
989
 
    print "mkdir -p", path
990
 
    if dry: return
991
 
    try:
992
 
        os.makedirs(path)
993
 
    except OSError, (err, msg):
994
 
        if err != errno.EEXIST:
995
 
            raise
996
 
 
997
 
def action_copytree(src, dst, dry):
998
 
    """Copies an entire directory tree. Symlinks are seen as normal files and
999
 
    copies of the entire file (not the link) are made. Creates all parent
1000
 
    directories as necessary.
1001
 
 
1002
 
    See shutil.copytree."""
1003
 
    action_remove(dst, dry)
1004
 
    print "cp -r", src, dst
1005
 
    if dry: return
1006
 
    shutil.copytree(src, dst, True)
1007
 
 
1008
 
def action_linktree(src, dst, dry):
1009
 
    """Hard-links an entire directory tree. Same as copytree but the created
1010
 
    files are hard-links not actual copies. Removes the existing destination.
1011
 
    """
1012
 
    action_remove(dst, dry)
1013
 
    print "<cp with hardlinks> -r", src, dst
1014
 
    if dry: return
1015
 
    common.makeuser.linktree(src, dst)
1016
 
 
1017
 
def action_copylist(srclist, dst, dry, srcdir="."):
1018
 
    """Copies all files in a list to a new location. The files in the list
1019
 
    are read relative to the current directory, and their destinations are the
1020
 
    same paths relative to dst. Creates all parent directories as necessary.
1021
 
    srcdir is "." by default, can be overridden.
1022
 
    """
1023
 
    for srcfile in srclist:
1024
 
        dstfile = os.path.join(dst, srcfile)
1025
 
        srcfile = os.path.join(srcdir, srcfile)
1026
 
        dstdir = os.path.split(dstfile)[0]
1027
 
        if not os.path.isdir(dstdir):
1028
 
            action_mkdir(dstdir, dry)
1029
 
        print "cp -f", srcfile, dstfile
1030
 
        if not dry:
1031
 
            try:
1032
 
                shutil.copyfile(srcfile, dstfile)
1033
 
                shutil.copymode(srcfile, dstfile)
1034
 
            except shutil.Error:
1035
 
                pass
1036
 
 
1037
 
def action_copyfile(src, dst, dry):
1038
 
    """Copies one file to a new location. Creates all parent directories
1039
 
    as necessary.
1040
 
    Warn if file not found.
1041
 
    """
1042
 
    dstdir = os.path.split(dst)[0]
1043
 
    if not os.path.isdir(dstdir):
1044
 
        action_mkdir(dstdir, dry)
1045
 
    print "cp -f", src, dst
1046
 
    if not dry:
1047
 
        try:
1048
 
            shutil.copyfile(src, dst)
1049
 
            shutil.copymode(src, dst)
1050
 
        except (shutil.Error, IOError), e:
1051
 
            print "Warning: " + str(e)
1052
 
 
1053
 
def action_symlink(src, dst, dry):
1054
 
    """Creates a symlink in a given location. Creates all parent directories
1055
 
    as necessary.
1056
 
    """
1057
 
    dstdir = os.path.split(dst)[0]
1058
 
    if not os.path.isdir(dstdir):
1059
 
        action_mkdir(dstdir, dry)
1060
 
    # Delete existing file
1061
 
    if os.path.exists(dst):
1062
 
        os.remove(dst)
1063
 
    print "ln -fs", src, dst
1064
 
    if not dry:
1065
 
        os.symlink(src, dst)
1066
 
 
1067
 
def action_append(ivle_pth, ivle_www):
1068
 
    file = open(ivle_pth, 'a+')
1069
 
    file.write(ivle_www + '\n')
1070
 
    file.close()
1071
 
 
1072
 
def action_chown_setuid(file, dry):
1073
 
    """Chowns a file to root, and sets the setuid bit on the file.
1074
 
    Calling this function requires the euid to be root.
1075
 
    The actual mode of path is set to: rws--s--s
1076
 
    """
1077
 
    print "chown root:root", file
1078
 
    if not dry:
1079
 
        os.chown(file, 0, 0)
1080
 
    print "chmod a+xs", file
1081
 
    print "chmod u+rw", file
1082
 
    if not dry:
1083
 
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1084
 
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
1085
 
 
1086
 
def action_chmod_x(file, dry):
1087
 
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
1088
 
    print "chmod 755", file
1089
 
    if not dry:
1090
 
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
1091
 
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
1092
 
 
1093
 
def query_user(default, prompt):
1094
 
    """Prompts the user for a string, which is read from a line of stdin.
1095
 
    Exits silently if EOF is encountered. Returns the string, with spaces
1096
 
    removed from the beginning and end.
1097
 
 
1098
 
    Returns default if a 0-length line (after spaces removed) was read.
1099
 
    """
1100
 
    sys.stdout.write('%s\n    (default: "%s")\n>' % (prompt, default))
1101
 
    try:
1102
 
        val = sys.stdin.readline()
1103
 
    except KeyboardInterrupt:
1104
 
        # Ctrl+C
1105
 
        sys.stdout.write("\n")
1106
 
        sys.exit(1)
1107
 
    sys.stdout.write("\n")
1108
 
    # If EOF, exit
1109
 
    if val == '': sys.exit(1)
1110
 
    # If empty line, return default
1111
 
    val = val.strip()
1112
 
    if val == '': return default
1113
 
    return val
1114
 
 
1115
 
def filter_mutate(function, list):
1116
 
    """Like built-in filter, but mutates the given list instead of returning a
1117
 
    new one. Returns None."""
1118
 
    i = len(list)-1
1119
 
    while i >= 0:
1120
 
        # Delete elements which do not match
1121
 
        if not function(list[i]):
1122
 
            del list[i]
1123
 
        i -= 1
 
90
        sys.exit(1)
 
91
    return oper_func
1124
92
 
1125
93
if __name__ == "__main__":
1126
94
    sys.exit(main())
 
95