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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: drtomc
  • Date: 2008-02-03 22:43:33 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:392
Fix another glitch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
# It is called with at least one argument, which specifies which operation to
26
26
# take.
27
27
 
28
 
# setup.py conf [args]
 
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]
29
35
# Configures IVLE with machine-specific details, most notably, various paths.
30
36
# Either prompts the administrator for these details or accepts them as
31
37
# command-line args.
42
48
#   (eg. python and Python libs, ld.so, etc).
43
49
# Generates .pyc files for all the IVLE .py files.
44
50
 
45
 
# setup.py listmake (for developer use only)
46
 
# Recurses through the source tree and builds a list of all files which should
47
 
# be copied upon installation. This should be run by the developer before
48
 
# cutting a distribution, and the listfile it generates should be included in
49
 
# the distribution, avoiding the administrator having to run it.
50
 
 
51
51
# setup.py install [--nojail] [--dry|n]
52
52
# (Requires root)
53
53
# Create target install directory ($target).
57
57
# Copy www/ to $target.
58
58
# Copy jail/ to jails template directory (unless --nojail specified).
59
59
 
60
 
# TODO: List in help, and handle, args for the conf operation
61
 
 
62
60
import os
 
61
import stat
 
62
import shutil
63
63
import sys
64
64
import getopt
65
65
import string
66
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
class ConfigOption:
 
137
    """A configuration option; one of the things written to conf.py."""
 
138
    def __init__(self, option_name, default, prompt, comment):
 
139
        """Creates a configuration option.
 
140
        option_name: Name of the variable in conf.py. Also name of the
 
141
            command-line argument to setup.py conf.
 
142
        default: Default value for this variable.
 
143
        prompt: (Short) string presented during the interactive prompt in
 
144
            setup.py conf.
 
145
        comment: (Long) comment string stored in conf.py. Each line of this
 
146
            string should begin with a '#'.
 
147
        """
 
148
        self.option_name = option_name
 
149
        self.default = default
 
150
        self.prompt = prompt
 
151
        self.comment = comment
 
152
 
 
153
# Configuration options, defaults and descriptions
 
154
config_options = []
 
155
config_options.append(ConfigOption("root_dir", "/ivle",
 
156
    """Root directory where IVLE is located (in URL space):""",
 
157
    """
 
158
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
 
159
# with this).
 
160
# eg. "/" or "/ivle"."""))
 
161
config_options.append(ConfigOption("ivle_install_dir", "/opt/ivle",
 
162
    'Root directory where IVLE will be installed (on the local file '
 
163
    'system):',
 
164
    """
 
165
# In the local file system, where IVLE is actually installed.
 
166
# This directory should contain the "www" and "bin" directories."""))
 
167
config_options.append(ConfigOption("jail_base", "/home/informatics/jails",
 
168
    """Root directory where the jails (containing user files) are stored
 
169
(on the local file system):""",
 
170
    """
 
171
# In the local file system, where are the student/user file spaces located.
 
172
# The user jails are expected to be located immediately in subdirectories of
 
173
# this location."""))
 
174
config_options.append(ConfigOption("subjects_base",
 
175
    "/home/informatics/subjects",
 
176
    """Root directory where the subject directories (containing worksheets
 
177
and other per-subject files) are stored (on the local file system):""",
 
178
    """
 
179
# In the local file system, where are the per-subject file spaces located.
 
180
# The individual subject directories are expected to be located immediately
 
181
# in subdirectories of this location."""))
 
182
config_options.append(ConfigOption("public_host", "public.localhost",
 
183
    """Hostname which will cause the server to go into "public mode",
 
184
providing login-free access to student's published work:""",
 
185
    """
 
186
# The server goes into "public mode" if the browser sends a request with this
 
187
# host. This is for security reasons - we only serve public student files on a
 
188
# separate domain to the main IVLE site.
 
189
# Public mode does not use cookies, and serves only public content.
 
190
# Private mode (normal mode) requires login, and only serves files relevant to
 
191
# the logged-in user."""))
 
192
config_options.append(ConfigOption("allowed_uids", "33",
 
193
    """UID of the web server process which will run IVLE.
 
194
Only this user may execute the trampoline. May specify multiple users as
 
195
a comma-separated list.
 
196
    (eg. "1002,78")""",
 
197
    """
 
198
# The User-ID of the web server process which will run IVLE, and any other
 
199
# users who are allowed to run the trampoline. This is stores as a string of
 
200
# comma-separated integers, simply because it is not used within Python, only
 
201
# used by the setup program to write to conf.h (see setup.py config)."""))
 
202
config_options.append(ConfigOption("db_host", "localhost",
 
203
    """PostgreSQL Database config
 
204
==========================
 
205
Hostname of the DB server:""",
 
206
    """
 
207
### PostgreSQL Database config ###
 
208
# Database server hostname"""))
 
209
config_options.append(ConfigOption("db_port", "5432",
 
210
    """Port of the DB server:""",
 
211
    """
 
212
# Database server port"""))
 
213
config_options.append(ConfigOption("db_dbname", "ivle",
 
214
    """Database name:""",
 
215
    """
 
216
# Database name"""))
 
217
config_options.append(ConfigOption("db_user", "postgres",
 
218
    """Username for DB server login:""",
 
219
    """
 
220
# Database username"""))
 
221
config_options.append(ConfigOption("db_password", "",
 
222
    """Password for DB server login:
 
223
    (Caution: This password is stored in plaintext in www/conf/conf.py)""",
 
224
    """
 
225
# Database password"""))
67
226
 
68
227
# Try importing existing conf, but if we can't just set up defaults
69
228
# The reason for this is that these settings are used by other phases
71
230
# Also this allows you to hit Return to accept the existing value.
72
231
try:
73
232
    confmodule = __import__("www/conf/conf")
74
 
    root_dir = confmodule.root_dir
75
 
    ivle_install_dir = confmodule.ivle_install_dir
76
 
    jail_base = confmodule.jail_base
 
233
    for opt in config_options:
 
234
        try:
 
235
            globals()[opt.option_name] = confmodule.__dict__[opt.option_name]
 
236
        except:
 
237
            globals()[opt.option_name] = opt.default
77
238
except ImportError:
78
239
    # Just set reasonable defaults
79
 
    root_dir = "/ivle"
80
 
    ivle_install_dir = "/opt/ivle"
81
 
    jail_base = "/home/informatics/jails"
82
 
# Always defaults
83
 
allowed_uids = "0"
 
240
    for opt in config_options:
 
241
        globals()[opt.option_name] = opt.default
 
242
 
 
243
# Try importing install_list, but don't fail if we can't, because listmake can
 
244
# function without it.
 
245
try:
 
246
    import install_list
 
247
except:
 
248
    pass
 
249
 
 
250
# Mime types which will automatically be placed in the list by listmake.
 
251
# Note that listmake is not intended to be run by the final user (the system
 
252
# administrator who installs this), so the developers can customize the list
 
253
# as necessary, and include it in the distribution.
 
254
listmake_mimetypes = ['text/x-python', 'text/html',
 
255
    'application/x-javascript', 'application/javascript',
 
256
    'text/css', 'image/png', 'application/xml']
84
257
 
85
258
# Main function skeleton from Guido van Rossum
86
259
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
87
260
 
88
 
class Usage(Exception):
89
 
    def __init__(self, msg):
90
 
        self.msg = msg
91
 
 
92
261
def main(argv=None):
93
262
    if argv is None:
94
263
        argv = sys.argv
112
281
        help([])
113
282
        return 1
114
283
 
 
284
    # Disallow run as root unless installing
 
285
    if (operation != 'install' and operation != 'updatejails'
 
286
        and os.geteuid() == 0):
 
287
        print >>sys.stderr, "I do not want to run this stage as root."
 
288
        print >>sys.stderr, "Please run as a normal user."
 
289
        return 1
115
290
    # Call the requested operation's function
116
291
    try:
117
 
        return {
 
292
        oper_func = {
118
293
            'help' : help,
119
 
            'conf' : conf,
 
294
            'config' : conf,
120
295
            'build' : build,
121
296
            'listmake' : listmake,
122
297
            'install' : install,
123
 
        }[operation](argv[2:])
 
298
            'updatejails' : updatejails,
 
299
        }[operation]
124
300
    except KeyError:
125
301
        print >>sys.stderr, (
126
302
            """Invalid operation '%s'. Try python setup.py help."""
127
303
            % operation)
128
 
 
129
 
    try:
130
 
        try:
131
 
            opts, args = getopt.getopt(argv[1:], "h", ["help"])
132
 
        except getopt.error, msg:
133
 
            raise Usage(msg)
134
 
        # more code, unchanged
135
 
    except Usage, err:
136
 
        print >>sys.stderr, err.msg
137
 
        print >>sys.stderr, "for help use --help"
138
 
        return 2
 
304
        return 1
 
305
    return oper_func(argv[2:])
139
306
 
140
307
# Operation functions
141
308
 
144
311
        print """Usage: python setup.py operation [args]
145
312
Operation (and args) can be:
146
313
    help [operation]
147
 
    conf [args]
 
314
    listmake (developer use only)
 
315
    config [args]
148
316
    build
149
 
    install [--nojail] [-n|--dry]
 
317
    install [--nojail] [--nosubjects] [-n|--dry]
150
318
"""
151
319
        return 1
152
320
    elif len(args) != 1:
158
326
    if operation == 'help':
159
327
        print """python setup.py help [operation]
160
328
Prints the usage message or detailed help on an operation, then exits."""
161
 
    elif operation == 'conf':
162
 
        print """python setup.py conf [args]
 
329
    elif operation == 'listmake':
 
330
        print """python setup.py listmake
 
331
(For developer use only)
 
332
Recurses through the source tree and builds a list of all files which should
 
333
be copied upon installation. This should be run by the developer before
 
334
cutting a distribution, and the listfile it generates should be included in
 
335
the distribution, avoiding the administrator having to run it."""
 
336
    elif operation == 'config':
 
337
        print """python setup.py config [args]
163
338
Configures IVLE with machine-specific details, most notably, various paths.
164
339
Either prompts the administrator for these details or accepts them as
165
 
command-line args.
 
340
command-line args. Will be interactive only if there are no arguments given.
 
341
Takes defaults from existing conf file if it exists.
 
342
 
 
343
To run IVLE out of the source directory (allowing development without having
 
344
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
 
345
directory, and run build/install one time.
 
346
 
166
347
Creates www/conf/conf.py and trampoline/conf.h.
167
 
Args are:
 
348
 
 
349
Args are:"""
 
350
        for opt in config_options:
 
351
            print "    --" + opt.option_name
 
352
        print """As explained in the interactive prompt or conf.py.
168
353
"""
169
354
    elif operation == 'build':
170
 
        print """python setup.py build
 
355
        print """python -O setup.py build [--dry|-n]
171
356
Compiles all files and sets up a jail template in the source directory.
 
357
-O is recommended to cause compilation to be optimised.
172
358
Details:
173
359
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
174
360
Creates jail/.
176
362
Copies console/ to a location within the jail.
177
363
Copies OS programs and files to corresponding locations within the jail
178
364
  (eg. python and Python libs, ld.so, etc).
179
 
Generates .pyc files for all the IVLE .py files."""
180
 
    elif operation == 'listmake':
181
 
        print """python setup.py listmake
182
 
(For developer use only)
183
 
Recurses through the source tree and builds a list of all files which should
184
 
be copied upon installation. This should be run by the developer before
185
 
cutting a distribution, and the listfile it generates should be included in
186
 
the distribution, avoiding the administrator having to run it."""
 
365
Generates .pyc or .pyo files for all the IVLE .py files.
 
366
 
 
367
--dry | -n  Print out the actions but don't do anything."""
187
368
    elif operation == 'install':
188
 
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
369
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
189
370
(Requires root)
190
371
Create target install directory ($target).
191
372
Create $target/bin.
193
374
chown and chmod the installed trampoline.
194
375
Copy www/ to $target.
195
376
Copy jail/ to jails template directory (unless --nojail specified).
196
 
 
197
 
--nojail    Do not copy the jail.
 
377
Copy subjects/ to subjects directory (unless --nosubjects specified).
 
378
 
 
379
--nojail        Do not copy the jail.
 
380
--nosubjects    Do not copy the subjects.
 
381
--dry | -n  Print out the actions but don't do anything."""
 
382
    elif operation == 'updatejails':
 
383
        print """sudo python setup.py updatejails [--dry|-n]
 
384
(Requires root)
 
385
Copy jail/ to each subdirectory in jails directory.
 
386
 
198
387
--dry | -n  Print out the actions but don't do anything."""
199
388
    else:
200
389
        print >>sys.stderr, (
206
395
    # We build two separate lists, by walking www and console
207
396
    list_www = build_list_py_files('www')
208
397
    list_console = build_list_py_files('console')
 
398
    list_subjects = build_list_py_files('subjects', no_top_level=True)
209
399
    # Make sure that the files generated by conf are in the list
210
400
    # (since listmake is typically run before conf)
211
401
    if "www/conf/conf.py" not in list_www:
212
402
        list_www.append("www/conf/conf.py")
 
403
    # Make sure that console/python-console is in the list
 
404
    if "console/python-console" not in list_console:
 
405
        list_console.append("console/python-console")
213
406
    # Write these out to a file
214
407
    cwd = os.getcwd()
215
408
    # the files that will be created/overwritten
220
413
 
221
414
        file.write("""# IVLE Configuration File
222
415
# install_list.py
223
 
# Provides lists of all Python files to be installed by `setup.py install'.
 
416
# Provides lists of all files to be installed by `setup.py install' from
 
417
# certain directories.
 
418
# Note that any files with the given filename plus 'c' or 'o' (that is,
 
419
# compiled .pyc or .pyo files) will be copied as well.
224
420
 
225
 
# List of all installable Python files in www directory.
 
421
# List of all installable files in www directory.
226
422
list_www = """)
227
423
        writelist_pretty(file, list_www)
228
424
        file.write("""
229
 
# List of all installable Python files in console directory.
 
425
# List of all installable files in console directory.
230
426
list_console = """)
231
427
        writelist_pretty(file, list_console)
 
428
        file.write("""
 
429
# List of all installable files in subjects directory.
 
430
# This is to install sample subjects and material.
 
431
list_subjects = """)
 
432
        writelist_pretty(file, list_subjects)
232
433
 
233
434
        file.close()
234
435
    except IOError, (errno, strerror):
245
446
 
246
447
    return 0
247
448
 
248
 
def build_list_py_files(dir):
 
449
def build_list_py_files(dir, no_top_level=False):
249
450
    """Builds a list of all py files found in a directory and its
250
 
    subdirectories. Returns this as a list of strings."""
 
451
    subdirectories. Returns this as a list of strings.
 
452
    no_top_level=True means the file paths will not include the top-level
 
453
    directory.
 
454
    """
251
455
    pylist = []
252
456
    for (dirpath, dirnames, filenames) in os.walk(dir):
253
457
        # Exclude directories beginning with a '.' (such as '.svn')
254
458
        filter_mutate(lambda x: x[0] != '.', dirnames)
255
459
        # All *.py files are added to the list
256
460
        pylist += [os.path.join(dirpath, item) for item in filenames
257
 
            if item.endswith('.py')]
 
461
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
462
    if no_top_level:
 
463
        for i in range(0, len(pylist)):
 
464
            _, pylist[i] = pylist[i].split(os.sep, 1)
258
465
    return pylist
259
466
 
260
467
def writelist_pretty(file, list):
268
475
        file.write(']\n')
269
476
 
270
477
def conf(args):
271
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
478
    global db_port
272
479
    # Set up some variables
273
480
 
274
481
    cwd = os.getcwd()
276
483
    conffile = os.path.join(cwd, "www/conf/conf.py")
277
484
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
278
485
 
279
 
    # Fixed config options that we don't ask the admin
280
 
 
281
 
    default_app = "dummy"
282
 
 
283
 
    print """This tool will create the following files:
 
486
    # Get command-line arguments to avoid asking questions.
 
487
 
 
488
    optnames = []
 
489
    for opt in config_options:
 
490
        optnames.append(opt.option_name + "=")
 
491
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
 
492
 
 
493
    if args != []:
 
494
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
 
495
        return 2
 
496
 
 
497
    if opts == []:
 
498
        # Interactive mode. Prompt the user for all the values.
 
499
 
 
500
        print """This tool will create the following files:
284
501
    %s
285
502
    %s
286
503
prompting you for details about your configuration. The file will be
289
506
Please hit Ctrl+C now if you do not wish to do this.
290
507
""" % (conffile, conf_hfile)
291
508
 
292
 
    # Get information from the administrator
293
 
    # If EOF is encountered at any time during the questioning, just exit
294
 
    # silently
 
509
        # Get information from the administrator
 
510
        # If EOF is encountered at any time during the questioning, just exit
 
511
        # silently
295
512
 
296
 
    root_dir = query_user(root_dir,
297
 
    """Root directory where IVLE is located (in URL space):""")
298
 
    ivle_install_dir = query_user(ivle_install_dir,
299
 
    'Root directory where IVLE will be installed (on the local file '
300
 
    'system):')
301
 
    jail_base = query_user(jail_base,
302
 
    """Root directory where the jails (containing user files) are stored
303
 
(on the local file system):""")
304
 
    allowed_uids = query_user(allowed_uids,
305
 
    """UID of the web server process which will run IVLE.
306
 
Only this user may execute the trampoline. May specify multiple users as
307
 
a comma-separated list.
308
 
    (eg. "1002,78")""")
 
513
        for opt in config_options:
 
514
            globals()[opt.option_name] = \
 
515
                query_user(globals()[opt.option_name], opt.prompt)
 
516
    else:
 
517
        opts = dict(opts)
 
518
        # Non-interactive mode. Parse the options.
 
519
        for opt in config_options:
 
520
            if '--' + opt.option_name in opts:
 
521
                globals()[opt.option_name] = opts['--' + opt.option_name]
309
522
 
310
523
    # Error handling on input values
311
 
 
312
524
    try:
313
 
        allowed_uids = map(int, allowed_uids.split(','))
 
525
        allowed_uids_list = map(int, allowed_uids.split(','))
314
526
    except ValueError:
315
527
        print >>sys.stderr, (
316
528
        "Invalid UID list (%s).\n"
317
529
        "Must be a comma-separated list of integers." % allowed_uids)
318
530
        return 1
 
531
    try:
 
532
        db_port = int(db_port)
 
533
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
534
    except ValueError:
 
535
        print >>sys.stderr, (
 
536
        "Invalid DB port (%s).\n"
 
537
        "Must be an integer between 0 and 65535." % repr(db_port))
 
538
        return 1
319
539
 
320
540
    # Write www/conf/conf.py
321
541
 
326
546
# conf.py
327
547
# Miscellaneous application settings
328
548
 
329
 
 
330
 
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
331
 
# with this).
332
 
# eg. "/" or "/ivle".
333
 
root_dir = "%s"
334
 
 
335
 
# In the local file system, where IVLE is actually installed.
336
 
# This directory should contain the "www" and "bin" directories.
337
 
ivle_install_dir = "%s"
338
 
 
339
 
# In the local file system, where are the student/user file spaces located.
340
 
# The user jails are expected to be located immediately in subdirectories of
341
 
# this location.
342
 
jail_base = "%s"
343
 
 
344
 
# Which application to load by default (if the user navigates to the top level
345
 
# of the site). This is the app's URL name.
346
 
# Note that if this app requires authentication, the user will first be
347
 
# presented with the login screen.
348
 
default_app = "%s"
349
 
""" % (root_dir, ivle_install_dir, jail_base, default_app))
 
549
""")
 
550
        for opt in config_options:
 
551
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
552
                repr(globals()[opt.option_name])))
350
553
 
351
554
        conf.close()
352
555
    except IOError, (errno, strerror):
378
581
 * (Note that root is an implicit member of this list).
379
582
 */
380
583
static const int allowed_uids[] = { %s };
381
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
584
""" % (jail_base, repr(allowed_uids_list)[1:-1]))
382
585
 
383
586
        conf.close()
384
587
    except IOError, (errno, strerror):
395
598
    return 0
396
599
 
397
600
def build(args):
398
 
    dry = False     # Set to True later if --dry
 
601
    # Get "dry" variable from command line
 
602
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
603
    opts = dict(opts)
 
604
    dry = '-n' in opts or '--dry' in opts
 
605
 
 
606
    if dry:
 
607
        print "Dry run (no actions will be executed\n"
399
608
 
400
609
    # Compile the trampoline
401
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
402
 
        'trampoline/trampoline.c'], dry)
 
610
    curdir = os.getcwd()
 
611
    os.chdir('trampoline')
 
612
    action_runprog('make', [], dry)
 
613
    os.chdir(curdir)
403
614
 
404
615
    # Create the jail and its subdirectories
405
 
    action_mkdir('jail')
406
 
    action_mkdir('jail/bin')
407
 
    action_mkdir('jail/lib')
408
 
    action_mkdir('jail/usr/bin')
409
 
    action_mkdir('jail/usr/lib')
410
 
    action_mkdir('jail/opt/ivle')
411
 
    action_mkdir('jail/home')
412
 
    action_mkdir('jail/tmp')
413
 
 
414
 
    # TODO: Copy console into the jail
415
 
    # TODO: Copy operating system files into the jail
416
 
    # TODO: Compile .py files into .pyc files
 
616
    # Note: Other subdirs will be made by copying files
 
617
    action_mkdir('jail', dry)
 
618
    action_mkdir('jail/home', dry)
 
619
    action_mkdir('jail/tmp', dry)
 
620
 
 
621
    # Copy all console and operating system files into the jail
 
622
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
 
623
    copy_os_files_jail(dry)
 
624
    # Chmod the python console
 
625
    action_chmod_x('jail/opt/ivle/console/python-console', dry)
 
626
    
 
627
 
 
628
    # Compile .py files into .pyc or .pyo files
 
629
    compileall.compile_dir('www', quiet=True)
 
630
    compileall.compile_dir('console', quiet=True)
417
631
 
418
632
    return 0
419
633
 
 
634
def copy_os_files_jail(dry):
 
635
    """Copies necessary Operating System files from their usual locations
 
636
    into the jail/ directory of the cwd."""
 
637
    # Currently source paths are configured for Ubuntu.
 
638
    for filename in JAIL_FILES:
 
639
        copy_file_to_jail(filename, dry)
 
640
    for src, dst in JAIL_LINKS.items():
 
641
        action_symlink(src, dst, dry)
 
642
    for src, dst in JAIL_COPYTREES.items():
 
643
        action_copytree(src, dst, dry)
 
644
 
 
645
def copy_file_to_jail(src, dry):
 
646
    """Copies a single file from an absolute location into the same location
 
647
    within the jail. src must begin with a '/'. The jail will be located
 
648
    in a 'jail' subdirectory of the current path."""
 
649
    action_copyfile(src, 'jail' + src, dry)
 
650
 
420
651
def install(args):
421
 
    print "Install"
 
652
    # Get "dry" and "nojail" variables from command line
 
653
    (opts, args) = getopt.gnu_getopt(args, "n",
 
654
        ['dry', 'nojail', 'nosubjects'])
 
655
    opts = dict(opts)
 
656
    dry = '-n' in opts or '--dry' in opts
 
657
    nojail = '--nojail' in opts
 
658
    nosubjects = '--nosubjects' in opts
 
659
 
 
660
    if dry:
 
661
        print "Dry run (no actions will be executed\n"
 
662
 
 
663
    if not dry and os.geteuid() != 0:
 
664
        print >>sys.stderr, "Must be root to run install"
 
665
        print >>sys.stderr, "(I need to chown some files)."
 
666
        return 1
 
667
 
 
668
    # Create the target (install) directory
 
669
    action_mkdir(ivle_install_dir, dry)
 
670
 
 
671
    # Create bin and copy the compiled files there
 
672
    action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
 
673
    tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
 
674
    action_copyfile('trampoline/trampoline', tramppath, dry)
 
675
    # chown trampoline to root and set setuid bit
 
676
    action_chown_setuid(tramppath, dry)
 
677
 
 
678
    # Copy the www directory using the list
 
679
    action_copylist(install_list.list_www, ivle_install_dir, dry)
 
680
 
 
681
    if not nojail:
 
682
        # Copy the local jail directory built by the build action
 
683
        # to the jails template directory (it will be used as a template
 
684
        # for all the students' jails).
 
685
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
686
    if not nosubjects:
 
687
        # Copy the subjects directory across
 
688
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
689
            srcdir="./subjects")
 
690
 
 
691
    # Append IVLE path to ivle.pth in python site packages
 
692
    # (Unless it's already there)
 
693
    ivle_pth = os.path.join(sys.prefix,
 
694
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
695
    ivle_www = os.path.join(ivle_install_dir, "www")
 
696
    write_ivle_pth = True
 
697
    try:
 
698
        file = open(ivle_pth, 'r')
 
699
        for line in file:
 
700
            if line.strip() == ivle_www:
 
701
                write_ivle_pth = False
 
702
                break
 
703
    except (IOError, OSError):
 
704
        pass
 
705
    if write_ivle_pth:
 
706
        action_append(ivle_pth, ivle_www)
 
707
 
 
708
    return 0
 
709
 
 
710
def updatejails(args):
 
711
    # Get "dry" variable from command line
 
712
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
713
    opts = dict(opts)
 
714
    dry = '-n' in opts or '--dry' in opts
 
715
 
 
716
    if dry:
 
717
        print "Dry run (no actions will be executed\n"
 
718
 
 
719
    if not dry and os.geteuid() != 0:
 
720
        print >>sys.stderr, "Must be root to run install"
 
721
        print >>sys.stderr, "(I need to chown some files)."
 
722
        return 1
 
723
 
 
724
    # Update the template jail directory in case it hasn't been installed
 
725
    # recently.
 
726
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
727
 
 
728
    # Re-link all the files in all students jails.
 
729
    for dir in os.listdir(jail_base):
 
730
        if dir == 'template': continue
 
731
        # First back up the student's home directory
 
732
        temp_home = os.tmpnam()
 
733
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
734
        # Delete the student's jail and relink the jail files
 
735
        action_linktree(os.path.join(jail_base, 'template'),
 
736
            os.path.join(jail_base, dir), dry)
 
737
        # Restore the student's home directory
 
738
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
739
        # Set up the user's home directory just in case they don't have a
 
740
        # directory for this yet
 
741
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
 
742
 
422
743
    return 0
423
744
 
424
745
# The actions call Python os functions but print actions and handle dryness.
450
771
    if ret != 0:
451
772
        raise RunError(prog, ret)
452
773
 
453
 
def action_mkdir(path):
 
774
def action_rename(src, dst, dry):
 
775
    """Calls rename. Deletes the target if it already exists."""
 
776
    if os.access(dst, os.F_OK):
 
777
        print "rm -r", dst
 
778
        if not dry:
 
779
            shutil.rmtree(dst, True)
 
780
    print "mv ", src, dst
 
781
    if dry: return
 
782
    try:
 
783
        os.rename(src, dst)
 
784
    except OSError, (err, msg):
 
785
        if err != errno.EEXIST:
 
786
            raise
 
787
 
 
788
def action_mkdir(path, dry):
454
789
    """Calls mkdir. Silently ignored if the directory already exists.
455
790
    Creates all parent directories as necessary."""
456
791
    print "mkdir -p", path
461
796
        if err != errno.EEXIST:
462
797
            raise
463
798
 
 
799
def action_copytree(src, dst, dry):
 
800
    """Copies an entire directory tree. Symlinks are seen as normal files and
 
801
    copies of the entire file (not the link) are made. Creates all parent
 
802
    directories as necessary.
 
803
 
 
804
    See shutil.copytree."""
 
805
    if os.access(dst, os.F_OK):
 
806
        print "rm -r", dst
 
807
        if not dry:
 
808
            shutil.rmtree(dst, True)
 
809
    print "cp -r", src, dst
 
810
    if dry: return
 
811
    shutil.copytree(src, dst, True)
 
812
 
 
813
def action_linktree(src, dst, dry):
 
814
    """Hard-links an entire directory tree. Same as copytree but the created
 
815
    files are hard-links not actual copies. Removes the existing destination.
 
816
    """
 
817
    if os.access(dst, os.F_OK):
 
818
        print "rm -r", dst
 
819
        if not dry:
 
820
            shutil.rmtree(dst, True)
 
821
    print "<cp with hardlinks> -r", src, dst
 
822
    if dry: return
 
823
    common.makeuser.linktree(src, dst)
 
824
 
 
825
def action_copylist(srclist, dst, dry, srcdir="."):
 
826
    """Copies all files in a list to a new location. The files in the list
 
827
    are read relative to the current directory, and their destinations are the
 
828
    same paths relative to dst. Creates all parent directories as necessary.
 
829
    srcdir is "." by default, can be overridden.
 
830
    """
 
831
    for srcfile in srclist:
 
832
        dstfile = os.path.join(dst, srcfile)
 
833
        srcfile = os.path.join(srcdir, srcfile)
 
834
        dstdir = os.path.split(dstfile)[0]
 
835
        if not os.path.isdir(dstdir):
 
836
            action_mkdir(dstdir, dry)
 
837
        print "cp -f", srcfile, dstfile
 
838
        if not dry:
 
839
            try:
 
840
                shutil.copyfile(srcfile, dstfile)
 
841
                shutil.copymode(srcfile, dstfile)
 
842
            except shutil.Error:
 
843
                pass
 
844
 
 
845
def action_copyfile(src, dst, dry):
 
846
    """Copies one file to a new location. Creates all parent directories
 
847
    as necessary.
 
848
    Warn if file not found.
 
849
    """
 
850
    dstdir = os.path.split(dst)[0]
 
851
    if not os.path.isdir(dstdir):
 
852
        action_mkdir(dstdir, dry)
 
853
    print "cp -f", src, dst
 
854
    if not dry:
 
855
        try:
 
856
            shutil.copyfile(src, dst)
 
857
            shutil.copymode(src, dst)
 
858
        except (shutil.Error, IOError), e:
 
859
            print "Warning: " + str(e)
 
860
 
 
861
def action_symlink(src, dst, dry):
 
862
    """Creates a symlink in a given location. Creates all parent directories
 
863
    as necessary.
 
864
    """
 
865
    dstdir = os.path.split(dst)[0]
 
866
    if not os.path.isdir(dstdir):
 
867
        action_mkdir(dstdir, dry)
 
868
    # Delete existing file
 
869
    if os.path.exists(dst):
 
870
        os.remove(dst)
 
871
    print "ln -fs", src, dst
 
872
    if not dry:
 
873
        os.symlink(src, dst)
 
874
 
 
875
def action_append(ivle_pth, ivle_www):
 
876
    file = open(ivle_pth, 'a+')
 
877
    file.write(ivle_www + '\n')
 
878
    file.close()
 
879
 
 
880
def action_chown_setuid(file, dry):
 
881
    """Chowns a file to root, and sets the setuid bit on the file.
 
882
    Calling this function requires the euid to be root.
 
883
    The actual mode of path is set to: rws--s--s
 
884
    """
 
885
    print "chown root:root", file
 
886
    if not dry:
 
887
        os.chown(file, 0, 0)
 
888
    print "chmod a+xs", file
 
889
    print "chmod u+rw", file
 
890
    if not dry:
 
891
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
 
892
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
 
893
 
 
894
def action_chmod_x(file, dry):
 
895
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
896
    print "chmod 755", file
 
897
    if not dry:
 
898
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
899
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
900
 
464
901
def query_user(default, prompt):
465
902
    """Prompts the user for a string, which is read from a line of stdin.
466
903
    Exits silently if EOF is encountered. Returns the string, with spaces