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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-02-06 03:08:52 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:431
Tested a little bit more thoroughly. Still not completely thoroughly, but
enouch, hopefully, and fixed, again, makeuser.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
# cutting a distribution, and the listfile it generates should be included in
32
32
# the distribution, avoiding the administrator having to run it.
33
33
 
34
 
# setup.py conf [args]
 
34
# setup.py config [args]
35
35
# Configures IVLE with machine-specific details, most notably, various paths.
36
36
# Either prompts the administrator for these details or accepts them as
37
37
# command-line args.
38
 
# Creates www/conf/conf.py and trampoline/conf.h.
 
38
# Creates lib/conf/conf.py and trampoline/conf.h.
39
39
 
40
40
# setup.py build
41
41
# Compiles all files and sets up a jail template in the source directory.
68
68
import compileall
69
69
import getopt
70
70
 
 
71
# Import modules from the website is tricky since they're in the www
 
72
# directory.
 
73
sys.path.append(os.path.join(os.getcwd(), 'lib'))
 
74
import conf
 
75
import common.makeuser
 
76
 
 
77
# Determine which Python version (2.4 or 2.5, for example) we are running,
 
78
# and use that as the filename to the Python directory.
 
79
# Just get the first 3 characters of sys.version.
 
80
PYTHON_VERSION = sys.version[0:3]
 
81
 
 
82
# Operating system files to copy over into the jail.
 
83
# These will be copied from the given place on the OS file system into the
 
84
# same place within the jail.
 
85
JAIL_FILES = [
 
86
    '/lib/ld-linux.so.2',
 
87
    '/lib/tls/i686/cmov/libc.so.6',
 
88
    '/lib/tls/i686/cmov/libdl.so.2',
 
89
    '/lib/tls/i686/cmov/libm.so.6',
 
90
    '/lib/tls/i686/cmov/libpthread.so.0',
 
91
    '/lib/tls/i686/cmov/libutil.so.1',
 
92
    '/etc/ld.so.conf',
 
93
    '/etc/ld.so.cache',
 
94
    # These 2 files do not exist in Ubuntu
 
95
    #'/etc/ld.so.preload',
 
96
    #'/etc/ld.so.nohwcap',
 
97
    # UNIX commands
 
98
    '/usr/bin/strace',
 
99
    '/bin/ls',
 
100
    '/bin/echo',
 
101
    # Needed by python
 
102
    '/usr/bin/python%s' % PYTHON_VERSION,
 
103
    # Needed by 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("problems_base",
 
183
    "/home/informatics/problems",
 
184
    """Root directory where the problem directories (containing
 
185
subject-independent problem sheets) are stored (on the local file
 
186
system):""",
 
187
    """
 
188
# In the local file system, where are the subject-independent problem sheet
 
189
# file spaces located."""))
 
190
config_options.append(ConfigOption("public_host", "public.localhost",
 
191
    """Hostname which will cause the server to go into "public mode",
 
192
providing login-free access to student's published work:""",
 
193
    """
 
194
# The server goes into "public mode" if the browser sends a request with this
 
195
# host. This is for security reasons - we only serve public student files on a
 
196
# separate domain to the main IVLE site.
 
197
# Public mode does not use cookies, and serves only public content.
 
198
# Private mode (normal mode) requires login, and only serves files relevant to
 
199
# the logged-in user."""))
 
200
config_options.append(ConfigOption("allowed_uids", "33",
 
201
    """UID of the web server process which will run IVLE.
 
202
Only this user may execute the trampoline. May specify multiple users as
 
203
a comma-separated list.
 
204
    (eg. "1002,78")""",
 
205
    """
 
206
# The User-ID of the web server process which will run IVLE, and any other
 
207
# users who are allowed to run the trampoline. This is stores as a string of
 
208
# comma-separated integers, simply because it is not used within Python, only
 
209
# used by the setup program to write to conf.h (see setup.py config)."""))
 
210
config_options.append(ConfigOption("db_host", "localhost",
 
211
    """PostgreSQL Database config
 
212
==========================
 
213
Hostname of the DB server:""",
 
214
    """
 
215
### PostgreSQL Database config ###
 
216
# Database server hostname"""))
 
217
config_options.append(ConfigOption("db_port", "5432",
 
218
    """Port of the DB server:""",
 
219
    """
 
220
# Database server port"""))
 
221
config_options.append(ConfigOption("db_dbname", "ivle",
 
222
    """Database name:""",
 
223
    """
 
224
# Database name"""))
 
225
config_options.append(ConfigOption("db_user", "postgres",
 
226
    """Username for DB server login:""",
 
227
    """
 
228
# Database username"""))
 
229
config_options.append(ConfigOption("db_password", "",
 
230
    """Password for DB server login:
 
231
    (Caution: This password is stored in plaintext in lib/conf/conf.py)""",
 
232
    """
 
233
# Database password"""))
 
234
 
71
235
# Try importing existing conf, but if we can't just set up defaults
72
236
# The reason for this is that these settings are used by other phases
73
237
# of setup besides conf, so we need to know them.
74
238
# Also this allows you to hit Return to accept the existing value.
75
239
try:
76
 
    confmodule = __import__("www/conf/conf")
77
 
    root_dir = confmodule.root_dir
78
 
    ivle_install_dir = confmodule.ivle_install_dir
79
 
    jail_base = confmodule.jail_base
 
240
    confmodule = __import__("lib/conf/conf")
 
241
    for opt in config_options:
 
242
        try:
 
243
            globals()[opt.option_name] = confmodule.__dict__[opt.option_name]
 
244
        except:
 
245
            globals()[opt.option_name] = opt.default
80
246
except ImportError:
81
247
    # Just set reasonable defaults
82
 
    root_dir = "/ivle"
83
 
    ivle_install_dir = "/opt/ivle"
84
 
    jail_base = "/home/informatics/jails"
85
 
# Always defaults
86
 
allowed_uids = "0"
 
248
    for opt in config_options:
 
249
        globals()[opt.option_name] = opt.default
87
250
 
88
251
# Try importing install_list, but don't fail if we can't, because listmake can
89
252
# function without it.
98
261
# as necessary, and include it in the distribution.
99
262
listmake_mimetypes = ['text/x-python', 'text/html',
100
263
    'application/x-javascript', 'application/javascript',
101
 
    'text/css', 'image/png']
 
264
    'text/css', 'image/png', 'application/xml']
102
265
 
103
266
# Main function skeleton from Guido van Rossum
104
267
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
127
290
        return 1
128
291
 
129
292
    # Disallow run as root unless installing
130
 
    if operation != 'install' and os.geteuid() == 0:
 
293
    if (operation != 'install' and operation != 'updatejails'
 
294
        and os.geteuid() == 0):
131
295
        print >>sys.stderr, "I do not want to run this stage as root."
132
296
        print >>sys.stderr, "Please run as a normal user."
133
297
        return 1
135
299
    try:
136
300
        oper_func = {
137
301
            'help' : help,
138
 
            'conf' : conf,
 
302
            'config' : conf,
139
303
            'build' : build,
140
304
            'listmake' : listmake,
141
305
            'install' : install,
 
306
            'updatejails' : updatejails,
142
307
        }[operation]
143
308
    except KeyError:
144
309
        print >>sys.stderr, (
155
320
Operation (and args) can be:
156
321
    help [operation]
157
322
    listmake (developer use only)
158
 
    conf [args]
 
323
    config [args]
159
324
    build
160
 
    install [--nojail] [-n|--dry]
 
325
    install [--nojail] [--nosubjects] [-n|--dry]
161
326
"""
162
327
        return 1
163
328
    elif len(args) != 1:
176
341
be copied upon installation. This should be run by the developer before
177
342
cutting a distribution, and the listfile it generates should be included in
178
343
the distribution, avoiding the administrator having to run it."""
179
 
    elif operation == 'conf':
180
 
        print """python setup.py conf [args]
 
344
    elif operation == 'config':
 
345
        print """python setup.py config [args]
181
346
Configures IVLE with machine-specific details, most notably, various paths.
182
347
Either prompts the administrator for these details or accepts them as
183
348
command-line args. Will be interactive only if there are no arguments given.
187
352
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
188
353
directory, and run build/install one time.
189
354
 
190
 
Creates www/conf/conf.py and trampoline/conf.h.
 
355
Creates lib/conf/conf.py and trampoline/conf.h.
191
356
 
192
 
Args are:
193
 
    --root_dir
194
 
    --ivle_install_dir
195
 
    --jail_base
196
 
    --allowed_uids
197
 
As explained in the interactive prompt or conf.py.
 
357
Args are:"""
 
358
        for opt in config_options:
 
359
            print "    --" + opt.option_name
 
360
        print """As explained in the interactive prompt or conf.py.
198
361
"""
199
362
    elif operation == 'build':
200
363
        print """python -O setup.py build [--dry|-n]
211
374
 
212
375
--dry | -n  Print out the actions but don't do anything."""
213
376
    elif operation == 'install':
214
 
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
377
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
215
378
(Requires root)
216
379
Create target install directory ($target).
217
380
Create $target/bin.
219
382
chown and chmod the installed trampoline.
220
383
Copy www/ to $target.
221
384
Copy jail/ to jails template directory (unless --nojail specified).
222
 
 
223
 
--nojail    Do not copy the jail.
 
385
Copy subjects/ to subjects directory (unless --nosubjects specified).
 
386
 
 
387
--nojail        Do not copy the jail.
 
388
--nosubjects    Do not copy the subjects and problems directories.
 
389
--dry | -n  Print out the actions but don't do anything."""
 
390
    elif operation == 'updatejails':
 
391
        print """sudo python setup.py updatejails [--dry|-n]
 
392
(Requires root)
 
393
Copy jail/ to each subdirectory in jails directory.
 
394
 
224
395
--dry | -n  Print out the actions but don't do anything."""
225
396
    else:
226
397
        print >>sys.stderr, (
231
402
def listmake(args):
232
403
    # We build two separate lists, by walking www and console
233
404
    list_www = build_list_py_files('www')
234
 
    list_console = build_list_py_files('console')
 
405
    list_lib = build_list_py_files('lib')
 
406
    list_scripts = build_list_py_files('scripts')
 
407
    list_subjects = build_list_py_files('subjects', no_top_level=True)
 
408
    list_problems = build_list_py_files('problems', no_top_level=True)
235
409
    # Make sure that the files generated by conf are in the list
236
410
    # (since listmake is typically run before conf)
237
 
    if "www/conf/conf.py" not in list_www:
238
 
        list_www.append("www/conf/conf.py")
 
411
    if "lib/conf/conf.py" not in list_lib:
 
412
        list_lib.append("lib/conf/conf.py")
239
413
    # Make sure that console/python-console is in the list
240
 
    if "console/python-console" not in list_console:
241
 
        list_console.append("console/python-console")
 
414
    if "scripts/python-console" not in list_scripts:
 
415
        list_scripts.append("scripts/python-console")
 
416
    if "scripts/fileservice" not in list_scripts:
 
417
        list_scripts.append("scripts/fileservice")
242
418
    # Write these out to a file
243
419
    cwd = os.getcwd()
244
420
    # the files that will be created/overwritten
258
434
list_www = """)
259
435
        writelist_pretty(file, list_www)
260
436
        file.write("""
261
 
# List of all installable files in console directory.
262
 
list_console = """)
263
 
        writelist_pretty(file, list_console)
 
437
# List of all installable files in lib directory.
 
438
list_lib = """)
 
439
        writelist_pretty(file, list_lib)
 
440
        file.write("""
 
441
# List of all installable files in scripts directory.
 
442
list_scripts = """)
 
443
        writelist_pretty(file, list_scripts)
 
444
        file.write("""
 
445
# List of all installable files in subjects directory.
 
446
# This is to install sample subjects and material.
 
447
list_subjects = """)
 
448
        writelist_pretty(file, list_subjects)
 
449
        file.write("""
 
450
# List of all installable files in problems directory.
 
451
# This is to install sample exercise material.
 
452
list_problems = """)
 
453
        writelist_pretty(file, list_problems)
264
454
 
265
455
        file.close()
266
456
    except IOError, (errno, strerror):
277
467
 
278
468
    return 0
279
469
 
280
 
def build_list_py_files(dir):
 
470
def build_list_py_files(dir, no_top_level=False):
281
471
    """Builds a list of all py files found in a directory and its
282
 
    subdirectories. Returns this as a list of strings."""
 
472
    subdirectories. Returns this as a list of strings.
 
473
    no_top_level=True means the file paths will not include the top-level
 
474
    directory.
 
475
    """
283
476
    pylist = []
284
477
    for (dirpath, dirnames, filenames) in os.walk(dir):
285
478
        # Exclude directories beginning with a '.' (such as '.svn')
287
480
        # All *.py files are added to the list
288
481
        pylist += [os.path.join(dirpath, item) for item in filenames
289
482
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
483
    if no_top_level:
 
484
        for i in range(0, len(pylist)):
 
485
            _, pylist[i] = pylist[i].split(os.sep, 1)
290
486
    return pylist
291
487
 
292
488
def writelist_pretty(file, list):
300
496
        file.write(']\n')
301
497
 
302
498
def conf(args):
303
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
499
    global db_port
304
500
    # Set up some variables
305
501
 
306
502
    cwd = os.getcwd()
307
503
    # the files that will be created/overwritten
308
 
    conffile = os.path.join(cwd, "www/conf/conf.py")
 
504
    conffile = os.path.join(cwd, "lib/conf/conf.py")
309
505
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
310
506
 
311
 
    # Fixed config options that we don't ask the admin
312
 
    default_app = "dummy"
313
 
 
314
507
    # Get command-line arguments to avoid asking questions.
315
508
 
316
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
317
 
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
 
509
    optnames = []
 
510
    for opt in config_options:
 
511
        optnames.append(opt.option_name + "=")
 
512
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
318
513
 
319
514
    if args != []:
320
515
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
336
531
        # If EOF is encountered at any time during the questioning, just exit
337
532
        # silently
338
533
 
339
 
        root_dir = query_user(root_dir,
340
 
        """Root directory where IVLE is located (in URL space):""")
341
 
        ivle_install_dir = query_user(ivle_install_dir,
342
 
        'Root directory where IVLE will be installed (on the local file '
343
 
        'system):')
344
 
        jail_base = query_user(jail_base,
345
 
        """Root directory where the jails (containing user files) are stored
346
 
(on the local file system):""")
347
 
        allowed_uids = query_user(allowed_uids,
348
 
        """UID of the web server process which will run IVLE.
349
 
Only this user may execute the trampoline. May specify multiple users as
350
 
a comma-separated list.
351
 
    (eg. "1002,78")""")
352
 
 
 
534
        for opt in config_options:
 
535
            globals()[opt.option_name] = \
 
536
                query_user(globals()[opt.option_name], opt.prompt)
353
537
    else:
354
538
        opts = dict(opts)
355
539
        # Non-interactive mode. Parse the options.
356
 
        if '--root_dir' in opts:
357
 
            root_dir = opts['--root_dir']
358
 
        if '--ivle_install_dir' in opts:
359
 
            ivle_install_dir = opts['--ivle_install_dir']
360
 
        if '--jail_base' in opts:
361
 
            jail_base = opts['--jail_base']
362
 
        if '--allowed_uids' in opts:
363
 
            allowed_uids = opts['--allowed_uids']
 
540
        for opt in config_options:
 
541
            if '--' + opt.option_name in opts:
 
542
                globals()[opt.option_name] = opts['--' + opt.option_name]
364
543
 
365
544
    # Error handling on input values
366
545
    try:
367
 
        allowed_uids = map(int, allowed_uids.split(','))
 
546
        allowed_uids_list = map(int, allowed_uids.split(','))
368
547
    except ValueError:
369
548
        print >>sys.stderr, (
370
549
        "Invalid UID list (%s).\n"
371
550
        "Must be a comma-separated list of integers." % allowed_uids)
372
551
        return 1
 
552
    try:
 
553
        db_port = int(db_port)
 
554
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
555
    except ValueError:
 
556
        print >>sys.stderr, (
 
557
        "Invalid DB port (%s).\n"
 
558
        "Must be an integer between 0 and 65535." % repr(db_port))
 
559
        return 1
373
560
 
374
 
    # Write www/conf/conf.py
 
561
    # Write lib/conf/conf.py
375
562
 
376
563
    try:
377
564
        conf = open(conffile, "w")
380
567
# conf.py
381
568
# Miscellaneous application settings
382
569
 
383
 
 
384
 
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
385
 
# with this).
386
 
# eg. "/" or "/ivle".
387
 
root_dir = "%s"
388
 
 
389
 
# In the local file system, where IVLE is actually installed.
390
 
# This directory should contain the "www" and "bin" directories.
391
 
ivle_install_dir = "%s"
392
 
 
393
 
# In the local file system, where are the student/user file spaces located.
394
 
# The user jails are expected to be located immediately in subdirectories of
395
 
# this location.
396
 
jail_base = "%s"
397
 
 
398
 
# Which application to load by default (if the user navigates to the top level
399
 
# of the site). This is the app's URL name.
400
 
# Note that if this app requires authentication, the user will first be
401
 
# presented with the login screen.
402
 
default_app = "%s"
403
 
""" % (root_dir, ivle_install_dir, jail_base, default_app))
 
570
""")
 
571
        for opt in config_options:
 
572
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
573
                repr(globals()[opt.option_name])))
404
574
 
405
575
        conf.close()
406
576
    except IOError, (errno, strerror):
407
577
        print "IO error(%s): %s" % (errno, strerror)
408
578
        sys.exit(1)
409
579
 
410
 
    print "Successfully wrote www/conf/conf.py"
 
580
    print "Successfully wrote lib/conf/conf.py"
411
581
 
412
582
    # Write trampoline/conf.h
413
583
 
432
602
 * (Note that root is an implicit member of this list).
433
603
 */
434
604
static const int allowed_uids[] = { %s };
435
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
605
""" % (jail_base, repr(allowed_uids_list)[1:-1]))
436
606
 
437
607
        conf.close()
438
608
    except IOError, (errno, strerror):
458
628
        print "Dry run (no actions will be executed\n"
459
629
 
460
630
    # Compile the trampoline
461
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
462
 
        'trampoline/trampoline.c'], dry)
 
631
    curdir = os.getcwd()
 
632
    os.chdir('trampoline')
 
633
    action_runprog('make', [], dry)
 
634
    os.chdir(curdir)
463
635
 
464
636
    # Create the jail and its subdirectories
465
637
    # Note: Other subdirs will be made by copying files
468
640
    action_mkdir('jail/tmp', dry)
469
641
 
470
642
    # Copy all console and operating system files into the jail
471
 
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
 
643
    action_copylist(install_list.list_scripts, 'jail/opt/ivle', dry)
472
644
    copy_os_files_jail(dry)
 
645
    # Chmod the python console
 
646
    action_chmod_x('jail/opt/ivle/scripts/python-console', dry)
 
647
    action_chmod_x('jail/opt/ivle/scripts/fileservice', dry)
 
648
    
 
649
    # Also copy the IVLE lib directory into the jail
 
650
    # This is necessary for running certain scripts
 
651
    action_copylist(install_list.list_lib, 'jail/opt/ivle', dry)
473
652
 
474
653
    # Compile .py files into .pyc or .pyo files
475
654
    compileall.compile_dir('www', quiet=True)
 
655
    compileall.compile_dir('lib', quiet=True)
476
656
    compileall.compile_dir('console', quiet=True)
 
657
    compileall.compile_dir('jail/opt/ivle/lib', quiet=True)
 
658
 
 
659
    # Set up ivle.pth inside the jail
 
660
    # Need to set /opt/ivle/lib to be on the import path
 
661
    ivle_pth = \
 
662
        "jail/usr/lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION
 
663
    f = open(ivle_pth, 'w')
 
664
    f.write('/opt/ivle/lib\n')
 
665
    f.close()
477
666
 
478
667
    return 0
479
668
 
481
670
    """Copies necessary Operating System files from their usual locations
482
671
    into the jail/ directory of the cwd."""
483
672
    # Currently source paths are configured for Ubuntu.
484
 
    copy_file_to_jail('/lib/ld-linux.so.2', dry)
485
 
    copy_file_to_jail('/lib/tls/i686/cmov/libc.so.6', dry)
486
 
    copy_file_to_jail('/lib/tls/i686/cmov/libdl.so.2', dry)
487
 
    copy_file_to_jail('/lib/tls/i686/cmov/libm.so.6', dry)
488
 
    copy_file_to_jail('/lib/tls/i686/cmov/libpthread.so.0', dry)
489
 
    copy_file_to_jail('/lib/tls/i686/cmov/libutil.so.1', dry)
490
 
    copy_file_to_jail('/usr/bin/python2.5', dry)
491
 
    action_symlink('python2.5', 'jail/usr/bin/python', dry)
492
 
    action_copytree('/usr/lib/python2.5', 'jail/usr/lib/python2.5', dry)
 
673
    for filename in JAIL_FILES:
 
674
        copy_file_to_jail(filename, dry)
 
675
    for src, dst in JAIL_LINKS.items():
 
676
        action_symlink(src, dst, dry)
 
677
    for src, dst in JAIL_COPYTREES.items():
 
678
        action_copytree(src, dst, dry)
493
679
 
494
680
def copy_file_to_jail(src, dry):
495
681
    """Copies a single file from an absolute location into the same location
499
685
 
500
686
def install(args):
501
687
    # Get "dry" and "nojail" variables from command line
502
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
 
688
    (opts, args) = getopt.gnu_getopt(args, "n",
 
689
        ['dry', 'nojail', 'nosubjects'])
503
690
    opts = dict(opts)
504
691
    dry = '-n' in opts or '--dry' in opts
505
692
    nojail = '--nojail' in opts
 
693
    nosubjects = '--nosubjects' in opts
506
694
 
507
695
    if dry:
508
696
        print "Dry run (no actions will be executed\n"
522
710
    # chown trampoline to root and set setuid bit
523
711
    action_chown_setuid(tramppath, dry)
524
712
 
525
 
    # Copy the www directory using the list
 
713
    # Copy the www and lib directories using the list
526
714
    action_copylist(install_list.list_www, ivle_install_dir, dry)
 
715
    action_copylist(install_list.list_lib, ivle_install_dir, dry)
527
716
 
528
717
    if not nojail:
529
718
        # Copy the local jail directory built by the build action
530
719
        # to the jails template directory (it will be used as a template
531
720
        # for all the students' jails).
532
721
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
722
    if not nosubjects:
 
723
        # Copy the subjects and problems directories across
 
724
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
725
            srcdir="./subjects")
 
726
        action_copylist(install_list.list_problems, problems_base, dry,
 
727
            srcdir="./problems")
 
728
 
 
729
    # Append IVLE path to ivle.pth in python site packages
 
730
    # (Unless it's already there)
 
731
    ivle_pth = os.path.join(sys.prefix,
 
732
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
733
    ivle_www = os.path.join(ivle_install_dir, "www")
 
734
    ivle_lib = os.path.join(ivle_install_dir, "lib")
 
735
    write_ivle_pth = True
 
736
    write_ivle_lib_pth = True
 
737
    try:
 
738
        file = open(ivle_pth, 'r')
 
739
        for line in file:
 
740
            if line.strip() == ivle_www:
 
741
                write_ivle_pth = False
 
742
            elif line.strip() == ivle_lib:
 
743
                write_ivle_lib_pth = False
 
744
        file.close()
 
745
    except (IOError, OSError):
 
746
        pass
 
747
    if write_ivle_pth:
 
748
        action_append(ivle_pth, ivle_www)
 
749
    if write_ivle_lib_pth:
 
750
        action_append(ivle_pth, ivle_lib)
 
751
 
 
752
    return 0
 
753
 
 
754
def updatejails(args):
 
755
    # Get "dry" variable from command line
 
756
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
757
    opts = dict(opts)
 
758
    dry = '-n' in opts or '--dry' in opts
 
759
 
 
760
    if dry:
 
761
        print "Dry run (no actions will be executed\n"
 
762
 
 
763
    if not dry and os.geteuid() != 0:
 
764
        print >>sys.stderr, "Must be root to run install"
 
765
        print >>sys.stderr, "(I need to chown some files)."
 
766
        return 1
 
767
 
 
768
    # Update the template jail directory in case it hasn't been installed
 
769
    # recently.
 
770
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
771
 
 
772
    # Re-link all the files in all students jails.
 
773
    for dir in os.listdir(jail_base):
 
774
        if dir == 'template': continue
 
775
        # First back up the student's home directory
 
776
        temp_home = os.tmpnam()
 
777
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
778
        # Delete the student's jail and relink the jail files
 
779
        action_linktree(os.path.join(jail_base, 'template'),
 
780
            os.path.join(jail_base, dir), dry)
 
781
        # Restore the student's home directory
 
782
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
783
        # Set up the user's home directory just in case they don't have a
 
784
        # directory for this yet
 
785
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
533
786
 
534
787
    return 0
535
788
 
562
815
    if ret != 0:
563
816
        raise RunError(prog, ret)
564
817
 
 
818
def action_rename(src, dst, dry):
 
819
    """Calls rename. Deletes the target if it already exists."""
 
820
    if os.access(dst, os.F_OK):
 
821
        print "rm -r", dst
 
822
        if not dry:
 
823
            shutil.rmtree(dst, True)
 
824
    print "mv ", src, dst
 
825
    if dry: return
 
826
    try:
 
827
        os.rename(src, dst)
 
828
    except OSError, (err, msg):
 
829
        if err != errno.EEXIST:
 
830
            raise
 
831
 
565
832
def action_mkdir(path, dry):
566
833
    """Calls mkdir. Silently ignored if the directory already exists.
567
834
    Creates all parent directories as necessary."""
587
854
    if dry: return
588
855
    shutil.copytree(src, dst, True)
589
856
 
590
 
def action_copylist(srclist, dst, dry):
 
857
def action_linktree(src, dst, dry):
 
858
    """Hard-links an entire directory tree. Same as copytree but the created
 
859
    files are hard-links not actual copies. Removes the existing destination.
 
860
    """
 
861
    if os.access(dst, os.F_OK):
 
862
        print "rm -r", dst
 
863
        if not dry:
 
864
            shutil.rmtree(dst, True)
 
865
    print "<cp with hardlinks> -r", src, dst
 
866
    if dry: return
 
867
    common.makeuser.linktree(src, dst)
 
868
 
 
869
def action_copylist(srclist, dst, dry, srcdir="."):
591
870
    """Copies all files in a list to a new location. The files in the list
592
871
    are read relative to the current directory, and their destinations are the
593
872
    same paths relative to dst. Creates all parent directories as necessary.
 
873
    srcdir is "." by default, can be overridden.
594
874
    """
595
875
    for srcfile in srclist:
596
876
        dstfile = os.path.join(dst, srcfile)
 
877
        srcfile = os.path.join(srcdir, srcfile)
597
878
        dstdir = os.path.split(dstfile)[0]
598
879
        if not os.path.isdir(dstdir):
599
880
            action_mkdir(dstdir, dry)
608
889
def action_copyfile(src, dst, dry):
609
890
    """Copies one file to a new location. Creates all parent directories
610
891
    as necessary.
 
892
    Warn if file not found.
611
893
    """
612
894
    dstdir = os.path.split(dst)[0]
613
895
    if not os.path.isdir(dstdir):
617
899
        try:
618
900
            shutil.copyfile(src, dst)
619
901
            shutil.copymode(src, dst)
620
 
        except shutil.Error:
621
 
            pass
 
902
        except (shutil.Error, IOError), e:
 
903
            print "Warning: " + str(e)
622
904
 
623
905
def action_symlink(src, dst, dry):
624
906
    """Creates a symlink in a given location. Creates all parent directories
634
916
    if not dry:
635
917
        os.symlink(src, dst)
636
918
 
 
919
def action_append(ivle_pth, ivle_www):
 
920
    file = open(ivle_pth, 'a+')
 
921
    file.write(ivle_www + '\n')
 
922
    file.close()
 
923
 
637
924
def action_chown_setuid(file, dry):
638
925
    """Chowns a file to root, and sets the setuid bit on the file.
639
926
    Calling this function requires the euid to be root.
648
935
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
649
936
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
650
937
 
 
938
def action_chmod_x(file, dry):
 
939
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
940
    print "chmod 755", file
 
941
    if not dry:
 
942
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
943
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
944
 
651
945
def query_user(default, prompt):
652
946
    """Prompts the user for a string, which is read from a line of stdin.
653
947
    Exits silently if EOF is encountered. Returns the string, with spaces