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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: Matt Giuca
  • Date: 2010-07-27 06:53:03 UTC
  • Revision ID: matt.giuca@gmail.com-20100727065303-gs7fn3gc3ccaqfux
Changed database schema 'semester' table. 'year' and 'semester' fields now allow any length, not just 4 or 1 respectively. (LP: #610330).

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 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 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
 
# Import modules from the website is tricky since they're in the www
72
 
# directory.
73
 
sys.path.append(os.path.join(os.getcwd(), 'www'))
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 matplotlib
104
 
    '/usr/lib/i686/cmov/libssl.so.0.9.8',
105
 
    '/usr/lib/i686/cmov/libcrypto.so.0.9.8',
106
 
    '/lib/tls/i686/cmov/libnsl.so.1',
107
 
    '/usr/lib/libz.so.1',
108
 
    '/usr/lib/atlas/liblapack.so.3',
109
 
    '/usr/lib/atlas/libblas.so.3',
110
 
    '/usr/lib/libg2c.so.0',
111
 
    '/usr/lib/libstdc++.so.6',
112
 
    '/usr/lib/libfreetype.so.6',
113
 
    '/usr/lib/libpng12.so.0',
114
 
    '/usr/lib/libBLT.2.4.so.8.4',
115
 
    '/usr/lib/libtk8.4.so.0',
116
 
    '/usr/lib/libtcl8.4.so.0',
117
 
    '/usr/lib/tcl8.4/init.tcl',
118
 
    '/usr/lib/libX11.so.6',
119
 
    '/usr/lib/libXau.so.6',
120
 
    '/usr/lib/libXdmcp.so.6',
121
 
    '/lib/libgcc_s.so.1',
122
 
    '/etc/matplotlibrc',
123
 
]
124
 
# Symlinks to make within the jail. Src mapped to dst.
125
 
JAIL_LINKS = {
126
 
    'python%s' % PYTHON_VERSION: 'jail/usr/bin/python',
127
 
}
128
 
# Trees to copy. Src mapped to dst (these will be passed to action_copytree).
129
 
JAIL_COPYTREES = {
130
 
    '/usr/lib/python%s' % PYTHON_VERSION:
131
 
        'jail/usr/lib/python%s' % PYTHON_VERSION,
132
 
    '/usr/share/matplotlib': 'jail/usr/share/matplotlib',
133
 
    '/etc/ld.so.conf.d': 'jail/etc/ld.so.conf.d',
134
 
}
135
 
 
136
 
# Try importing existing conf, but if we can't just set up defaults
137
 
# The reason for this is that these settings are used by other phases
138
 
# of setup besides conf, so we need to know them.
139
 
# Also this allows you to hit Return to accept the existing value.
140
 
try:
141
 
    confmodule = __import__("www/conf/conf")
142
 
    try:
143
 
        root_dir = confmodule.root_dir
144
 
    except:
145
 
        root_dir = "/ivle"
146
 
    try:
147
 
        ivle_install_dir = confmodule.ivle_install_dir
148
 
    except:
149
 
        ivle_install_dir = "/opt/ivle"
150
 
    try:
151
 
        public_host = confmodule.public_host
152
 
    except:
153
 
        public_host = "public.localhost"
154
 
    try:
155
 
        jail_base = confmodule.jail_base
156
 
    except:
157
 
        jail_base = "/home/informatics/jails"
158
 
    try:
159
 
        subjects_base = confmodule.subjects_base
160
 
    except:
161
 
        subjects_base = "/home/informatics/subjects"
162
 
except ImportError:
163
 
    # Just set reasonable defaults
164
 
    root_dir = "/ivle"
165
 
    ivle_install_dir = "/opt/ivle"
166
 
    public_host = "public.localhost"
167
 
    jail_base = "/home/informatics/jails"
168
 
    subjects_base = "/home/informatics/subjects"
169
 
# Always defaults
170
 
allowed_uids = "0"
171
 
 
172
 
# Try importing install_list, but don't fail if we can't, because listmake can
173
 
# function without it.
174
 
try:
175
 
    import install_list
176
 
except:
177
 
    pass
178
 
 
179
 
# Mime types which will automatically be placed in the list by listmake.
180
 
# Note that listmake is not intended to be run by the final user (the system
181
 
# administrator who installs this), so the developers can customize the list
182
 
# as necessary, and include it in the distribution.
183
 
listmake_mimetypes = ['text/x-python', 'text/html',
184
 
    'application/x-javascript', 'application/javascript',
185
 
    'text/css', 'image/png', 'application/xml']
186
 
 
187
 
# Main function skeleton from Guido van Rossum
188
 
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
 
30
import setup.build
 
31
import setup.install
189
32
 
190
33
def main(argv=None):
191
34
    if argv is None:
194
37
    # Print the opening spiel including the GPL notice
195
38
 
196
39
    print """IVLE - Informatics Virtual Learning Environment Setup
197
 
Copyright (C) 2007-2008 The University of Melbourne
 
40
Copyright (C) 2007-2009 The University of Melbourne
198
41
IVLE comes with ABSOLUTELY NO WARRANTY.
199
42
This is free software, and you are welcome to redistribute it
200
43
under certain conditions. See LICENSE.txt for details.
210
53
        help([])
211
54
        return 1
212
55
 
213
 
    # Disallow run as root unless installing
214
 
    if (operation != 'install' and operation != 'updatejails'
215
 
        and os.geteuid() == 0):
216
 
        print >>sys.stderr, "I do not want to run this stage as root."
217
 
        print >>sys.stderr, "Please run as a normal user."
218
 
        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):
219
74
    # Call the requested operation's function
220
75
    try:
221
76
        oper_func = {
222
77
            'help' : help,
223
 
            'config' : conf,
224
 
            'build' : build,
225
 
            'listmake' : listmake,
226
 
            'install' : install,
227
 
            'updatejails' : updatejails,
 
78
            'build' : setup.build.build,
 
79
            'install' : setup.install.install,
228
80
        }[operation]
229
81
    except KeyError:
230
82
        print >>sys.stderr, (
231
83
            """Invalid operation '%s'. Try python setup.py help."""
232
84
            % operation)
233
 
        return 1
234
 
    return oper_func(argv[2:])
235
 
 
236
 
# Operation functions
237
 
 
238
 
def help(args):
239
 
    if args == []:
240
 
        print """Usage: python setup.py operation [args]
241
 
Operation (and args) can be:
242
 
    help [operation]
243
 
    listmake (developer use only)
244
 
    config [args]
245
 
    build
246
 
    install [--nojail] [--nosubjects] [-n|--dry]
247
 
"""
248
 
        return 1
249
 
    elif len(args) != 1:
250
 
        print """Usage: python setup.py help [operation]"""
251
 
        return 2
252
 
    else:
253
 
        operation = args[0]
254
 
 
255
 
    if operation == 'help':
256
 
        print """python setup.py help [operation]
257
 
Prints the usage message or detailed help on an operation, then exits."""
258
 
    elif operation == 'listmake':
259
 
        print """python setup.py listmake
260
 
(For developer use only)
261
 
Recurses through the source tree and builds a list of all files which should
262
 
be copied upon installation. This should be run by the developer before
263
 
cutting a distribution, and the listfile it generates should be included in
264
 
the distribution, avoiding the administrator having to run it."""
265
 
    elif operation == 'config':
266
 
        print """python setup.py config [args]
267
 
Configures IVLE with machine-specific details, most notably, various paths.
268
 
Either prompts the administrator for these details or accepts them as
269
 
command-line args. Will be interactive only if there are no arguments given.
270
 
Takes defaults from existing conf file if it exists.
271
 
 
272
 
To run IVLE out of the source directory (allowing development without having
273
 
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
274
 
directory, and run build/install one time.
275
 
 
276
 
Creates www/conf/conf.py and trampoline/conf.h.
277
 
 
278
 
Args are:
279
 
    --root_dir
280
 
    --ivle_install_dir
281
 
    --public_host
282
 
    --jail_base
283
 
    --subjects_base
284
 
    --allowed_uids
285
 
As explained in the interactive prompt or conf.py.
286
 
"""
287
 
    elif operation == 'build':
288
 
        print """python -O setup.py build [--dry|-n]
289
 
Compiles all files and sets up a jail template in the source directory.
290
 
-O is recommended to cause compilation to be optimised.
291
 
Details:
292
 
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
293
 
Creates jail/.
294
 
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
295
 
Copies console/ to a location within the jail.
296
 
Copies OS programs and files to corresponding locations within the jail
297
 
  (eg. python and Python libs, ld.so, etc).
298
 
Generates .pyc or .pyo files for all the IVLE .py files.
299
 
 
300
 
--dry | -n  Print out the actions but don't do anything."""
301
 
    elif operation == 'install':
302
 
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
303
 
(Requires root)
304
 
Create target install directory ($target).
305
 
Create $target/bin.
306
 
Copy trampoline/trampoline to $target/bin.
307
 
chown and chmod the installed trampoline.
308
 
Copy www/ to $target.
309
 
Copy jail/ to jails template directory (unless --nojail specified).
310
 
Copy subjects/ to subjects directory (unless --nosubjects specified).
311
 
 
312
 
--nojail        Do not copy the jail.
313
 
--nosubjects    Do not copy the subjects.
314
 
--dry | -n  Print out the actions but don't do anything."""
315
 
    elif operation == 'updatejails':
316
 
        print """sudo python setup.py updatejails [--dry|-n]
317
 
(Requires root)
318
 
Copy jail/ to each subdirectory in jails directory.
319
 
 
320
 
--dry | -n  Print out the actions but don't do anything."""
321
 
    else:
322
 
        print >>sys.stderr, (
323
 
            """Invalid operation '%s'. Try python setup.py help."""
324
 
            % operation)
325
 
    return 1
326
 
 
327
 
def listmake(args):
328
 
    # We build two separate lists, by walking www and console
329
 
    list_www = build_list_py_files('www')
330
 
    list_console = build_list_py_files('console')
331
 
    list_subjects = build_list_py_files('subjects', no_top_level=True)
332
 
    # Make sure that the files generated by conf are in the list
333
 
    # (since listmake is typically run before conf)
334
 
    if "www/conf/conf.py" not in list_www:
335
 
        list_www.append("www/conf/conf.py")
336
 
    # Make sure that console/python-console is in the list
337
 
    if "console/python-console" not in list_console:
338
 
        list_console.append("console/python-console")
339
 
    # Write these out to a file
340
 
    cwd = os.getcwd()
341
 
    # the files that will be created/overwritten
342
 
    listfile = os.path.join(cwd, "install_list.py")
343
 
 
344
 
    try:
345
 
        file = open(listfile, "w")
346
 
 
347
 
        file.write("""# IVLE Configuration File
348
 
# install_list.py
349
 
# Provides lists of all files to be installed by `setup.py install' from
350
 
# certain directories.
351
 
# Note that any files with the given filename plus 'c' or 'o' (that is,
352
 
# compiled .pyc or .pyo files) will be copied as well.
353
 
 
354
 
# List of all installable files in www directory.
355
 
list_www = """)
356
 
        writelist_pretty(file, list_www)
357
 
        file.write("""
358
 
# List of all installable files in console directory.
359
 
list_console = """)
360
 
        writelist_pretty(file, list_console)
361
 
        file.write("""
362
 
# List of all installable files in subjects directory.
363
 
# This is to install sample subjects and material.
364
 
list_subjects = """)
365
 
        writelist_pretty(file, list_subjects)
366
 
 
367
 
        file.close()
368
 
    except IOError, (errno, strerror):
369
 
        print "IO error(%s): %s" % (errno, strerror)
370
 
        sys.exit(1)
371
 
 
372
 
    print "Successfully wrote install_list.py"
373
 
 
374
 
    print
375
 
    print ("You may modify the set of installable files before cutting the "
376
 
            "distribution:")
377
 
    print listfile
378
 
    print
379
 
 
380
 
    return 0
381
 
 
382
 
def build_list_py_files(dir, no_top_level=False):
383
 
    """Builds a list of all py files found in a directory and its
384
 
    subdirectories. Returns this as a list of strings.
385
 
    no_top_level=True means the file paths will not include the top-level
386
 
    directory.
387
 
    """
388
 
    pylist = []
389
 
    for (dirpath, dirnames, filenames) in os.walk(dir):
390
 
        # Exclude directories beginning with a '.' (such as '.svn')
391
 
        filter_mutate(lambda x: x[0] != '.', dirnames)
392
 
        # All *.py files are added to the list
393
 
        pylist += [os.path.join(dirpath, item) for item in filenames
394
 
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
395
 
    if no_top_level:
396
 
        for i in range(0, len(pylist)):
397
 
            _, pylist[i] = pylist[i].split(os.sep, 1)
398
 
    return pylist
399
 
 
400
 
def writelist_pretty(file, list):
401
 
    """Writes a list one element per line, to a file."""
402
 
    if list == []:
403
 
        file.write("[]\n")
404
 
    else:
405
 
        file.write('[\n')
406
 
        for elem in list:
407
 
            file.write('    %s,\n' % repr(elem))
408
 
        file.write(']\n')
409
 
 
410
 
def conf(args):
411
 
    global root_dir, ivle_install_dir, jail_base, subjects_base
412
 
    global public_host, allowed_uids
413
 
    # Set up some variables
414
 
 
415
 
    cwd = os.getcwd()
416
 
    # the files that will be created/overwritten
417
 
    conffile = os.path.join(cwd, "www/conf/conf.py")
418
 
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
419
 
 
420
 
    # Get command-line arguments to avoid asking questions.
421
 
 
422
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
423
 
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
424
 
 
425
 
    if args != []:
426
 
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
427
 
        return 2
428
 
 
429
 
    if opts == []:
430
 
        # Interactive mode. Prompt the user for all the values.
431
 
 
432
 
        print """This tool will create the following files:
433
 
    %s
434
 
    %s
435
 
prompting you for details about your configuration. The file will be
436
 
overwritten if it already exists. It will *not* install or deploy IVLE.
437
 
 
438
 
Please hit Ctrl+C now if you do not wish to do this.
439
 
""" % (conffile, conf_hfile)
440
 
 
441
 
        # Get information from the administrator
442
 
        # If EOF is encountered at any time during the questioning, just exit
443
 
        # silently
444
 
 
445
 
        root_dir = query_user(root_dir,
446
 
        """Root directory where IVLE is located (in URL space):""")
447
 
        ivle_install_dir = query_user(ivle_install_dir,
448
 
        'Root directory where IVLE will be installed (on the local file '
449
 
        'system):')
450
 
        jail_base = query_user(jail_base,
451
 
        """Root directory where the jails (containing user files) are stored
452
 
(on the local file system):""")
453
 
        subjects_base = query_user(subjects_base,
454
 
        """Root directory where the subject directories (containing worksheets
455
 
and other per-subject files) are stored (on the local file system):""")
456
 
        public_host = query_user(public_host,
457
 
        """Hostname which will cause the server to go into "public mode",
458
 
providing login-free access to student's published work:""")
459
 
        allowed_uids = query_user(allowed_uids,
460
 
        """UID of the web server process which will run IVLE.
461
 
Only this user may execute the trampoline. May specify multiple users as
462
 
a comma-separated list.
463
 
    (eg. "1002,78")""")
464
 
 
465
 
    else:
466
 
        opts = dict(opts)
467
 
        # Non-interactive mode. Parse the options.
468
 
        if '--root_dir' in opts:
469
 
            root_dir = opts['--root_dir']
470
 
        if '--ivle_install_dir' in opts:
471
 
            ivle_install_dir = opts['--ivle_install_dir']
472
 
        if '--jail_base' in opts:
473
 
            jail_base = opts['--jail_base']
474
 
        if '--subjects_base' in opts:
475
 
            jail_base = opts['--subjects_base']
476
 
        if '--public_host' in opts:
477
 
            public_host = opts['--public_host']
478
 
        if '--allowed_uids' in opts:
479
 
            allowed_uids = opts['--allowed_uids']
480
 
 
481
 
    # Error handling on input values
482
 
    try:
483
 
        allowed_uids = map(int, allowed_uids.split(','))
484
 
    except ValueError:
485
 
        print >>sys.stderr, (
486
 
        "Invalid UID list (%s).\n"
487
 
        "Must be a comma-separated list of integers." % allowed_uids)
488
 
        return 1
489
 
 
490
 
    # Write www/conf/conf.py
491
 
 
492
 
    try:
493
 
        conf = open(conffile, "w")
494
 
 
495
 
        conf.write("""# IVLE Configuration File
496
 
# conf.py
497
 
# Miscellaneous application settings
498
 
 
499
 
 
500
 
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
501
 
# with this).
502
 
# eg. "/" or "/ivle".
503
 
root_dir = "%s"
504
 
 
505
 
# In the local file system, where IVLE is actually installed.
506
 
# This directory should contain the "www" and "bin" directories.
507
 
ivle_install_dir = "%s"
508
 
 
509
 
# The server goes into "public mode" if the browser sends a request with this
510
 
# host. This is for security reasons - we only serve public student files on a
511
 
# separate domain to the main IVLE site.
512
 
# Public mode does not use cookies, and serves only public content.
513
 
# Private mode (normal mode) requires login, and only serves files relevant to
514
 
# the logged-in user.
515
 
public_host = "%s"
516
 
 
517
 
# In the local file system, where are the student/user file spaces located.
518
 
# The user jails are expected to be located immediately in subdirectories of
519
 
# this location.
520
 
jail_base = "%s"
521
 
 
522
 
# In the local file system, where are the per-subject file spaces located.
523
 
# The individual subject directories are expected to be located immediately
524
 
# in subdirectories of this location.
525
 
subjects_base = "%s"
526
 
""" % (root_dir, ivle_install_dir, public_host, jail_base, subjects_base))
527
 
 
528
 
        conf.close()
529
 
    except IOError, (errno, strerror):
530
 
        print "IO error(%s): %s" % (errno, strerror)
531
 
        sys.exit(1)
532
 
 
533
 
    print "Successfully wrote www/conf/conf.py"
534
 
 
535
 
    # Write trampoline/conf.h
536
 
 
537
 
    try:
538
 
        conf = open(conf_hfile, "w")
539
 
 
540
 
        conf.write("""/* IVLE Configuration File
541
 
 * conf.h
542
 
 * Administrator settings required by trampoline.
543
 
 * Note: trampoline will have to be rebuilt in order for changes to this file
544
 
 * to take effect.
545
 
 */
546
 
 
547
 
/* In the local file system, where are the jails located.
548
 
 * The trampoline does not allow the creation of a jail anywhere besides
549
 
 * jail_base or a subdirectory of jail_base.
550
 
 */
551
 
static const char* jail_base = "%s";
552
 
 
553
 
/* Which user IDs are allowed to run the trampoline.
554
 
 * This list should be limited to the web server user.
555
 
 * (Note that root is an implicit member of this list).
556
 
 */
557
 
static const int allowed_uids[] = { %s };
558
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
559
 
 
560
 
        conf.close()
561
 
    except IOError, (errno, strerror):
562
 
        print "IO error(%s): %s" % (errno, strerror)
563
 
        sys.exit(1)
564
 
 
565
 
    print "Successfully wrote trampoline/conf.h"
566
 
 
567
 
    print
568
 
    print "You may modify the configuration at any time by editing"
569
 
    print conffile
570
 
    print conf_hfile
571
 
    print
572
 
    return 0
573
 
 
574
 
def build(args):
575
 
    # Get "dry" variable from command line
576
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
577
 
    opts = dict(opts)
578
 
    dry = '-n' in opts or '--dry' in opts
579
 
 
580
 
    if dry:
581
 
        print "Dry run (no actions will be executed\n"
582
 
 
583
 
    # Compile the trampoline
584
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
585
 
        'trampoline/trampoline.c'], dry)
586
 
 
587
 
    # Create the jail and its subdirectories
588
 
    # Note: Other subdirs will be made by copying files
589
 
    action_mkdir('jail', dry)
590
 
    action_mkdir('jail/home', dry)
591
 
    action_mkdir('jail/tmp', dry)
592
 
 
593
 
    # Copy all console and operating system files into the jail
594
 
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
595
 
    copy_os_files_jail(dry)
596
 
    # Chmod the python console
597
 
    action_chmod_x('jail/opt/ivle/console/python-console', dry)
598
 
    
599
 
 
600
 
    # Compile .py files into .pyc or .pyo files
601
 
    compileall.compile_dir('www', quiet=True)
602
 
    compileall.compile_dir('console', quiet=True)
603
 
 
604
 
    return 0
605
 
 
606
 
def copy_os_files_jail(dry):
607
 
    """Copies necessary Operating System files from their usual locations
608
 
    into the jail/ directory of the cwd."""
609
 
    # Currently source paths are configured for Ubuntu.
610
 
    for filename in JAIL_FILES:
611
 
        copy_file_to_jail(filename, dry)
612
 
    for src, dst in JAIL_LINKS.items():
613
 
        action_symlink(src, dst, dry)
614
 
    for src, dst in JAIL_COPYTREES.items():
615
 
        action_copytree(src, dst, dry)
616
 
 
617
 
def copy_file_to_jail(src, dry):
618
 
    """Copies a single file from an absolute location into the same location
619
 
    within the jail. src must begin with a '/'. The jail will be located
620
 
    in a 'jail' subdirectory of the current path."""
621
 
    action_copyfile(src, 'jail' + src, dry)
622
 
 
623
 
def install(args):
624
 
    # Get "dry" and "nojail" variables from command line
625
 
    (opts, args) = getopt.gnu_getopt(args, "n",
626
 
        ['dry', 'nojail', 'nosubjects'])
627
 
    opts = dict(opts)
628
 
    dry = '-n' in opts or '--dry' in opts
629
 
    nojail = '--nojail' in opts
630
 
    nosubjects = '--nosubjects' in opts
631
 
 
632
 
    if dry:
633
 
        print "Dry run (no actions will be executed\n"
634
 
 
635
 
    if not dry and os.geteuid() != 0:
636
 
        print >>sys.stderr, "Must be root to run install"
637
 
        print >>sys.stderr, "(I need to chown some files)."
638
 
        return 1
639
 
 
640
 
    # Create the target (install) directory
641
 
    action_mkdir(ivle_install_dir, dry)
642
 
 
643
 
    # Create bin and copy the compiled files there
644
 
    action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
645
 
    tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
646
 
    action_copyfile('trampoline/trampoline', tramppath, dry)
647
 
    # chown trampoline to root and set setuid bit
648
 
    action_chown_setuid(tramppath, dry)
649
 
 
650
 
    # Copy the www directory using the list
651
 
    action_copylist(install_list.list_www, ivle_install_dir, dry)
652
 
 
653
 
    if not nojail:
654
 
        # Copy the local jail directory built by the build action
655
 
        # to the jails template directory (it will be used as a template
656
 
        # for all the students' jails).
657
 
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
658
 
    if not nosubjects:
659
 
        # Copy the subjects directory across
660
 
        action_copylist(install_list.list_subjects, subjects_base, dry,
661
 
            srcdir="./subjects")
662
 
 
663
 
    # Append IVLE path to ivle.pth in python site packages
664
 
    # (Unless it's already there)
665
 
    ivle_pth = os.path.join(sys.prefix,
666
 
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
667
 
    ivle_www = os.path.join(ivle_install_dir, "www")
668
 
    write_ivle_pth = True
669
 
    try:
670
 
        file = open(ivle_pth, 'r')
671
 
        for line in file:
672
 
            if line.strip() == ivle_www:
673
 
                write_ivle_pth = False
674
 
                break
675
 
    except (IOError, OSError):
676
 
        pass
677
 
    if write_ivle_pth:
678
 
        action_append(ivle_pth, ivle_www)
679
 
 
680
 
    return 0
681
 
 
682
 
def updatejails(args):
683
 
    # Get "dry" variable from command line
684
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
685
 
    opts = dict(opts)
686
 
    dry = '-n' in opts or '--dry' in opts
687
 
 
688
 
    if dry:
689
 
        print "Dry run (no actions will be executed\n"
690
 
 
691
 
    if not dry and os.geteuid() != 0:
692
 
        print >>sys.stderr, "Must be root to run install"
693
 
        print >>sys.stderr, "(I need to chown some files)."
694
 
        return 1
695
 
 
696
 
    # Update the template jail directory in case it hasn't been installed
697
 
    # recently.
698
 
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
699
 
 
700
 
    # Re-link all the files in all students jails.
701
 
    for dir in os.listdir(jail_base):
702
 
        if dir == 'template': continue
703
 
        # First back up the student's home directory
704
 
        temp_home = os.tmpnam()
705
 
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
706
 
        # Delete the student's jail and relink the jail files
707
 
        action_linktree(os.path.join(jail_base, 'template'),
708
 
            os.path.join(jail_base, dir), dry)
709
 
        # Restore the student's home directory
710
 
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
711
 
        # Set up the user's home directory just in case they don't have a
712
 
        # directory for this yet
713
 
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
714
 
 
715
 
    return 0
716
 
 
717
 
# The actions call Python os functions but print actions and handle dryness.
718
 
# May still throw os exceptions if errors occur.
719
 
 
720
 
class RunError:
721
 
    """Represents an error when running a program (nonzero return)."""
722
 
    def __init__(self, prog, retcode):
723
 
        self.prog = prog
724
 
        self.retcode = retcode
725
 
    def __str__(self):
726
 
        return str(self.prog) + " returned " + repr(self.retcode)
727
 
 
728
 
def action_runprog(prog, args, dry):
729
 
    """Runs a unix program. Searches in $PATH. Synchronous (waits for the
730
 
    program to return). Runs in the current environment. First prints the
731
 
    action as a "bash" line.
732
 
 
733
 
    Throws a RunError with a retcode of the return value of the program,
734
 
    if the program did not return 0.
735
 
 
736
 
    prog: String. Name of the program. (No path required, if in $PATH).
737
 
    args: [String]. Arguments to the program.
738
 
    dry: Bool. If True, prints but does not execute.
739
 
    """
740
 
    print prog, string.join(args, ' ')
741
 
    if dry: return
742
 
    ret = os.spawnvp(os.P_WAIT, prog, args)
743
 
    if ret != 0:
744
 
        raise RunError(prog, ret)
745
 
 
746
 
def action_rename(src, dst, dry):
747
 
    """Calls rename. Deletes the target if it already exists."""
748
 
    if os.access(dst, os.F_OK):
749
 
        print "rm -r", dst
750
 
        if not dry:
751
 
            shutil.rmtree(dst, True)
752
 
    print "mv ", src, dst
753
 
    if dry: return
754
 
    try:
755
 
        os.rename(src, dst)
756
 
    except OSError, (err, msg):
757
 
        if err != errno.EEXIST:
758
 
            raise
759
 
 
760
 
def action_mkdir(path, dry):
761
 
    """Calls mkdir. Silently ignored if the directory already exists.
762
 
    Creates all parent directories as necessary."""
763
 
    print "mkdir -p", path
764
 
    if dry: return
765
 
    try:
766
 
        os.makedirs(path)
767
 
    except OSError, (err, msg):
768
 
        if err != errno.EEXIST:
769
 
            raise
770
 
 
771
 
def action_copytree(src, dst, dry):
772
 
    """Copies an entire directory tree. Symlinks are seen as normal files and
773
 
    copies of the entire file (not the link) are made. Creates all parent
774
 
    directories as necessary.
775
 
 
776
 
    See shutil.copytree."""
777
 
    if os.access(dst, os.F_OK):
778
 
        print "rm -r", dst
779
 
        if not dry:
780
 
            shutil.rmtree(dst, True)
781
 
    print "cp -r", src, dst
782
 
    if dry: return
783
 
    shutil.copytree(src, dst, True)
784
 
 
785
 
def action_linktree(src, dst, dry):
786
 
    """Hard-links an entire directory tree. Same as copytree but the created
787
 
    files are hard-links not actual copies. Removes the existing destination.
788
 
    """
789
 
    if os.access(dst, os.F_OK):
790
 
        print "rm -r", dst
791
 
        if not dry:
792
 
            shutil.rmtree(dst, True)
793
 
    print "<cp with hardlinks> -r", src, dst
794
 
    if dry: return
795
 
    common.makeuser.linktree(src, dst)
796
 
 
797
 
def action_copylist(srclist, dst, dry, srcdir="."):
798
 
    """Copies all files in a list to a new location. The files in the list
799
 
    are read relative to the current directory, and their destinations are the
800
 
    same paths relative to dst. Creates all parent directories as necessary.
801
 
    srcdir is "." by default, can be overridden.
802
 
    """
803
 
    for srcfile in srclist:
804
 
        dstfile = os.path.join(dst, srcfile)
805
 
        srcfile = os.path.join(srcdir, srcfile)
806
 
        dstdir = os.path.split(dstfile)[0]
807
 
        if not os.path.isdir(dstdir):
808
 
            action_mkdir(dstdir, dry)
809
 
        print "cp -f", srcfile, dstfile
810
 
        if not dry:
811
 
            try:
812
 
                shutil.copyfile(srcfile, dstfile)
813
 
                shutil.copymode(srcfile, dstfile)
814
 
            except shutil.Error:
815
 
                pass
816
 
 
817
 
def action_copyfile(src, dst, dry):
818
 
    """Copies one file to a new location. Creates all parent directories
819
 
    as necessary.
820
 
    Warn if file not found.
821
 
    """
822
 
    dstdir = os.path.split(dst)[0]
823
 
    if not os.path.isdir(dstdir):
824
 
        action_mkdir(dstdir, dry)
825
 
    print "cp -f", src, dst
826
 
    if not dry:
827
 
        try:
828
 
            shutil.copyfile(src, dst)
829
 
            shutil.copymode(src, dst)
830
 
        except (shutil.Error, IOError), e:
831
 
            print "Warning: " + str(e)
832
 
 
833
 
def action_symlink(src, dst, dry):
834
 
    """Creates a symlink in a given location. Creates all parent directories
835
 
    as necessary.
836
 
    """
837
 
    dstdir = os.path.split(dst)[0]
838
 
    if not os.path.isdir(dstdir):
839
 
        action_mkdir(dstdir, dry)
840
 
    # Delete existing file
841
 
    if os.path.exists(dst):
842
 
        os.remove(dst)
843
 
    print "ln -fs", src, dst
844
 
    if not dry:
845
 
        os.symlink(src, dst)
846
 
 
847
 
def action_append(ivle_pth, ivle_www):
848
 
    file = open(ivle_pth, 'a+')
849
 
    file.write(ivle_www + '\n')
850
 
    file.close()
851
 
 
852
 
def action_chown_setuid(file, dry):
853
 
    """Chowns a file to root, and sets the setuid bit on the file.
854
 
    Calling this function requires the euid to be root.
855
 
    The actual mode of path is set to: rws--s--s
856
 
    """
857
 
    print "chown root:root", file
858
 
    if not dry:
859
 
        os.chown(file, 0, 0)
860
 
    print "chmod a+xs", file
861
 
    print "chmod u+rw", file
862
 
    if not dry:
863
 
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
864
 
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
865
 
 
866
 
def action_chmod_x(file, dry):
867
 
    """Chmod +xs a file (sets execute permission)."""
868
 
    print "chmod u+rwx", file
869
 
    if not dry:
870
 
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
871
 
 
872
 
def query_user(default, prompt):
873
 
    """Prompts the user for a string, which is read from a line of stdin.
874
 
    Exits silently if EOF is encountered. Returns the string, with spaces
875
 
    removed from the beginning and end.
876
 
 
877
 
    Returns default if a 0-length line (after spaces removed) was read.
878
 
    """
879
 
    sys.stdout.write('%s\n    (default: "%s")\n>' % (prompt, default))
880
 
    try:
881
 
        val = sys.stdin.readline()
882
 
    except KeyboardInterrupt:
883
 
        # Ctrl+C
884
 
        sys.stdout.write("\n")
885
 
        sys.exit(1)
886
 
    sys.stdout.write("\n")
887
 
    # If EOF, exit
888
 
    if val == '': sys.exit(1)
889
 
    # If empty line, return default
890
 
    val = val.strip()
891
 
    if val == '': return default
892
 
    return val
893
 
 
894
 
def filter_mutate(function, list):
895
 
    """Like built-in filter, but mutates the given list instead of returning a
896
 
    new one. Returns None."""
897
 
    i = len(list)-1
898
 
    while i >= 0:
899
 
        # Delete elements which do not match
900
 
        if not function(list[i]):
901
 
            del list[i]
902
 
        i -= 1
 
85
        sys.exit(1)
 
86
    return oper_func
903
87
 
904
88
if __name__ == "__main__":
905
89
    sys.exit(main())
 
90