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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-01-31 01:10:29 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:344
dispatch/ivle CSS foo.
The IVLE header is now a fixed height in em (previously was just intrinsic
    height).
This fixes the "tabs sometimes don't line up with the base line" display
problem, and also makes it much easier to add fixed or absolute content on the
rest of the page.

This was done by separating the tabs out into a separate div and making that
absolutely positioned. Now the header is exactly 5.3em.

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
# 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']
67
186
 
68
187
# Main function skeleton from Guido van Rossum
69
188
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
70
189
 
71
 
class Usage(Exception):
72
 
    def __init__(self, msg):
73
 
        self.msg = msg
74
 
 
75
190
def main(argv=None):
76
191
    if argv is None:
77
192
        argv = sys.argv
95
210
        help([])
96
211
        return 1
97
212
 
 
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
98
219
    # Call the requested operation's function
99
220
    try:
100
 
        return {
 
221
        oper_func = {
101
222
            'help' : help,
102
 
            'conf' : conf,
 
223
            'config' : conf,
103
224
            'build' : build,
104
225
            'listmake' : listmake,
105
226
            'install' : install,
106
 
        }[operation](argv[2:])
 
227
            'updatejails' : updatejails,
 
228
        }[operation]
107
229
    except KeyError:
108
230
        print >>sys.stderr, (
109
231
            """Invalid operation '%s'. Try python setup.py help."""
110
232
            % operation)
111
 
 
112
 
    try:
113
 
        try:
114
 
            opts, args = getopt.getopt(argv[1:], "h", ["help"])
115
 
        except getopt.error, msg:
116
 
            raise Usage(msg)
117
 
        # more code, unchanged
118
 
    except Usage, err:
119
 
        print >>sys.stderr, err.msg
120
 
        print >>sys.stderr, "for help use --help"
121
 
        return 2
 
233
        return 1
 
234
    return oper_func(argv[2:])
122
235
 
123
236
# Operation functions
124
237
 
127
240
        print """Usage: python setup.py operation [args]
128
241
Operation (and args) can be:
129
242
    help [operation]
130
 
    conf [args]
 
243
    listmake (developer use only)
 
244
    config [args]
131
245
    build
132
 
    install [--nojail] [-n|--dry]
 
246
    install [--nojail] [--nosubjects] [-n|--dry]
133
247
"""
134
248
        return 1
135
249
    elif len(args) != 1:
141
255
    if operation == 'help':
142
256
        print """python setup.py help [operation]
143
257
Prints the usage message or detailed help on an operation, then exits."""
144
 
    elif operation == 'conf':
145
 
        print """python setup.py conf [args]
 
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]
146
267
Configures IVLE with machine-specific details, most notably, various paths.
147
268
Either prompts the administrator for these details or accepts them as
148
 
command-line args.
 
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
 
149
276
Creates www/conf/conf.py and trampoline/conf.h.
 
277
 
150
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.
151
286
"""
152
287
    elif operation == 'build':
153
 
        print """python setup.py build
 
288
        print """python -O setup.py build [--dry|-n]
154
289
Compiles all files and sets up a jail template in the source directory.
 
290
-O is recommended to cause compilation to be optimised.
155
291
Details:
156
292
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
157
293
Creates jail/.
159
295
Copies console/ to a location within the jail.
160
296
Copies OS programs and files to corresponding locations within the jail
161
297
  (eg. python and Python libs, ld.so, etc).
162
 
Generates .pyc files for all the IVLE .py files."""
163
 
    elif operation == 'listmake':
164
 
        print """python setup.py listmake
165
 
(For developer use only)
166
 
Recurses through the source tree and builds a list of all files which should
167
 
be copied upon installation. This should be run by the developer before
168
 
cutting a distribution, and the listfile it generates should be included in
169
 
the distribution, avoiding the administrator having to run it."""
 
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."""
170
301
    elif operation == 'install':
171
 
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
302
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
172
303
(Requires root)
173
304
Create target install directory ($target).
174
305
Create $target/bin.
176
307
chown and chmod the installed trampoline.
177
308
Copy www/ to $target.
178
309
Copy jail/ to jails template directory (unless --nojail specified).
179
 
 
180
 
--nojail    Do not copy the jail.
 
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
 
181
320
--dry | -n  Print out the actions but don't do anything."""
182
321
    else:
183
322
        print >>sys.stderr, (
185
324
            % operation)
186
325
    return 1
187
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
 
188
410
def conf(args):
 
411
    global root_dir, ivle_install_dir, jail_base, subjects_base
 
412
    global public_host, allowed_uids
189
413
    # Set up some variables
190
414
 
191
415
    cwd = os.getcwd()
193
417
    conffile = os.path.join(cwd, "www/conf/conf.py")
194
418
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
195
419
 
196
 
    # Fixed config options that we don't ask the admin
197
 
 
198
 
    default_app = "dummy"
199
 
 
200
 
    print """This tool will create the following files:
 
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:
201
433
    %s
202
434
    %s
203
435
prompting you for details about your configuration. The file will be
206
438
Please hit Ctrl+C now if you do not wish to do this.
207
439
""" % (conffile, conf_hfile)
208
440
 
209
 
    # Get information from the administrator
210
 
    # If EOF is encountered at any time during the questioning, just exit
211
 
    # silently
212
 
 
213
 
    root_dir = query_user(
214
 
    """Root directory where IVLE is located (in URL space):
215
 
    (eg. "/" or "/ivle")""")
216
 
    ivle_install_dir = query_user(
217
 
    'Root directory where IVLE is located (on the local file system):\n'
218
 
    '(eg. "/home/informatics/ivle")')
219
 
    jail_base = query_user(
220
 
    """Root directory where user files are stored (on the local file system):
221
 
    (eg. "/home/informatics/jails")""")
222
 
    allowed_uid = query_user(
223
 
    """UID of the web server process which will run IVLE.
224
 
Only this user may execute the trampoline. You can configure multiple users
225
 
by manually editing conf.h.
226
 
    (eg. "1002")""")
 
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']
227
480
 
228
481
    # Error handling on input values
229
 
 
230
482
    try:
231
 
        allowed_uid = int(allowed_uid)
 
483
        allowed_uids = map(int, allowed_uids.split(','))
232
484
    except ValueError:
233
 
        print >>sys.stderr, "Invalid UID (%s)." % allowed_uid
 
485
        print >>sys.stderr, (
 
486
        "Invalid UID list (%s).\n"
 
487
        "Must be a comma-separated list of integers." % allowed_uids)
234
488
        return 1
235
489
 
236
490
    # Write www/conf/conf.py
252
506
# This directory should contain the "www" and "bin" directories.
253
507
ivle_install_dir = "%s"
254
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
 
255
517
# In the local file system, where are the student/user file spaces located.
256
518
# The user jails are expected to be located immediately in subdirectories of
257
519
# this location.
258
520
jail_base = "%s"
259
521
 
260
 
# Which application to load by default (if the user navigates to the top level
261
 
# of the site). This is the app's URL name.
262
 
# Note that if this app requires authentication, the user will first be
263
 
# presented with the login screen.
264
 
default_app = "%s"
265
 
""" % (root_dir, ivle_install_dir, jail_base, default_app))
 
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))
266
527
 
267
528
        conf.close()
268
529
    except IOError, (errno, strerror):
293
554
 * This list should be limited to the web server user.
294
555
 * (Note that root is an implicit member of this list).
295
556
 */
296
 
static const int allowed_uids[] = { %d };
297
 
""" % (jail_base, allowed_uid))
 
557
static const int allowed_uids[] = { %s };
 
558
""" % (jail_base, repr(allowed_uids)[1:-1]))
298
559
 
299
560
        conf.close()
300
561
    except IOError, (errno, strerror):
311
572
    return 0
312
573
 
313
574
def build(args):
314
 
    dry = False     # Set to True later if --dry
 
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"
315
582
 
316
583
    # Compile the trampoline
317
584
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
318
585
        'trampoline/trampoline.c'], dry)
319
586
 
320
587
    # Create the jail and its subdirectories
321
 
    action_mkdir('jail')
322
 
    action_mkdir('jail/bin')
323
 
    action_mkdir('jail/lib')
324
 
    action_mkdir('jail/usr')
325
 
    action_mkdir('jail/usr/bin')
326
 
    action_mkdir('jail/usr/lib')
327
 
    action_mkdir('jail/opt')
328
 
    action_mkdir('jail/home')
329
 
    action_mkdir('jail/tmp')
330
 
 
331
 
    # TODO: Copy console into the jail
332
 
    # TODO: Copy operating system files into the jail
333
 
    # TODO: Compile .py files into .pyc files
334
 
 
335
 
    return 0
336
 
 
337
 
def listmake(args):
338
 
    print "Listmake"
339
 
    return 0
 
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)
340
622
 
341
623
def install(args):
342
 
    print "Install"
 
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
 
343
715
    return 0
344
716
 
345
717
# The actions call Python os functions but print actions and handle dryness.
366
738
    dry: Bool. If True, prints but does not execute.
367
739
    """
368
740
    print prog, string.join(args, ' ')
369
 
    if not dry:
370
 
        ret = os.spawnvp(os.P_WAIT, prog, args)
371
 
        if ret != 0:
372
 
            raise RunError(prog, ret)
373
 
 
374
 
def action_mkdir(path):
375
 
    """Calls mkdir. Silently ignored if the directory already exists."""
376
 
    print "mkdir", path
377
 
    try:
378
 
        os.mkdir(path)
379
 
    except OSError, (err, msg):
380
 
        if err != errno.EEXIST:
381
 
            raise
382
 
 
383
 
def query_user(prompt):
 
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):
384
873
    """Prompts the user for a string, which is read from a line of stdin.
385
874
    Exits silently if EOF is encountered. Returns the string, with spaces
386
875
    removed from the beginning and end.
 
876
 
 
877
    Returns default if a 0-length line (after spaces removed) was read.
387
878
    """
388
 
    sys.stdout.write(prompt)
389
 
    sys.stdout.write("\n>")
 
879
    sys.stdout.write('%s\n    (default: "%s")\n>' % (prompt, default))
390
880
    try:
391
881
        val = sys.stdin.readline()
392
882
    except KeyboardInterrupt:
394
884
        sys.stdout.write("\n")
395
885
        sys.exit(1)
396
886
    sys.stdout.write("\n")
 
887
    # If EOF, exit
397
888
    if val == '': sys.exit(1)
398
 
    return val.strip()
 
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
399
903
 
400
904
if __name__ == "__main__":
401
905
    sys.exit(main())