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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-01-31 03:07:38 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:346
consolee/console.css:
    Restyled all the objects in console. Now populates the entire screen
    instead of a fixed-height area at the top. (Resizes to fit the canvas).
    You've no idea how tricky this is!

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
# Date:   12/12/2007
22
22
 
23
23
# This is a command-line application, for use by the administrator.
24
 
# This program is a frontend for the modules in the setup packages that 
25
 
# build and install IVLE in separate steps.
 
24
# This program configures, builds and installs IVLE in three separate steps.
26
25
# It is called with at least one argument, which specifies which operation to
27
26
# take.
28
27
 
 
28
# setup.py listmake (for developer use only)
 
29
# Recurses through the source tree and builds a list of all files which should
 
30
# be copied upon installation. This should be run by the developer before
 
31
# cutting a distribution, and the listfile it generates should be included in
 
32
# the distribution, avoiding the administrator having to run it.
 
33
 
 
34
# setup.py 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
29
63
import sys
30
 
import setup.build
31
 
import setup.install
 
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
32
189
 
33
190
def main(argv=None):
34
191
    if argv is None:
37
194
    # Print the opening spiel including the GPL notice
38
195
 
39
196
    print """IVLE - Informatics Virtual Learning Environment Setup
40
 
Copyright (C) 2007-2009 The University of Melbourne
 
197
Copyright (C) 2007-2008 The University of Melbourne
41
198
IVLE comes with ABSOLUTELY NO WARRANTY.
42
199
This is free software, and you are welcome to redistribute it
43
200
under certain conditions. See LICENSE.txt for details.
53
210
        help([])
54
211
        return 1
55
212
 
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):
 
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
74
219
    # Call the requested operation's function
75
220
    try:
76
221
        oper_func = {
77
222
            'help' : help,
78
 
            'build' : setup.build.build,
79
 
            'install' : setup.install.install,
 
223
            'config' : conf,
 
224
            'build' : build,
 
225
            'listmake' : listmake,
 
226
            'install' : install,
 
227
            'updatejails' : updatejails,
80
228
        }[operation]
81
229
    except KeyError:
82
230
        print >>sys.stderr, (
83
231
            """Invalid operation '%s'. Try python setup.py help."""
84
232
            % operation)
85
 
        sys.exit(1)
86
 
    return oper_func
 
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
87
903
 
88
904
if __name__ == "__main__":
89
905
    sys.exit(main())
90