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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: drtomc
  • Date: 2008-02-04 00:21:29 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:396
Alter the apache config so that session objects get stored on disk. We're arranging for the disk in question to be a shared NFS filesystem so users get logged in across all the machines in the cluster. Theoretically.

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.
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(), '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("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 www/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
240
    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
 
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.
189
354
 
190
355
Creates www/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, (
232
403
    # We build two separate lists, by walking www and console
233
404
    list_www = build_list_py_files('www')
234
405
    list_console = build_list_py_files('console')
 
406
    list_subjects = build_list_py_files('subjects', no_top_level=True)
 
407
    list_problems = build_list_py_files('problems', no_top_level=True)
235
408
    # Make sure that the files generated by conf are in the list
236
409
    # (since listmake is typically run before conf)
237
410
    if "www/conf/conf.py" not in list_www:
261
434
# List of all installable files in console directory.
262
435
list_console = """)
263
436
        writelist_pretty(file, list_console)
 
437
        file.write("""
 
438
# List of all installable files in subjects directory.
 
439
# This is to install sample subjects and material.
 
440
list_subjects = """)
 
441
        writelist_pretty(file, list_subjects)
 
442
        file.write("""
 
443
# List of all installable files in problems directory.
 
444
# This is to install sample exercise material.
 
445
list_problems = """)
 
446
        writelist_pretty(file, list_problems)
264
447
 
265
448
        file.close()
266
449
    except IOError, (errno, strerror):
277
460
 
278
461
    return 0
279
462
 
280
 
def build_list_py_files(dir):
 
463
def build_list_py_files(dir, no_top_level=False):
281
464
    """Builds a list of all py files found in a directory and its
282
 
    subdirectories. Returns this as a list of strings."""
 
465
    subdirectories. Returns this as a list of strings.
 
466
    no_top_level=True means the file paths will not include the top-level
 
467
    directory.
 
468
    """
283
469
    pylist = []
284
470
    for (dirpath, dirnames, filenames) in os.walk(dir):
285
471
        # Exclude directories beginning with a '.' (such as '.svn')
287
473
        # All *.py files are added to the list
288
474
        pylist += [os.path.join(dirpath, item) for item in filenames
289
475
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
476
    if no_top_level:
 
477
        for i in range(0, len(pylist)):
 
478
            _, pylist[i] = pylist[i].split(os.sep, 1)
290
479
    return pylist
291
480
 
292
481
def writelist_pretty(file, list):
300
489
        file.write(']\n')
301
490
 
302
491
def conf(args):
303
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
492
    global db_port
304
493
    # Set up some variables
305
494
 
306
495
    cwd = os.getcwd()
308
497
    conffile = os.path.join(cwd, "www/conf/conf.py")
309
498
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
310
499
 
311
 
    # Fixed config options that we don't ask the admin
312
 
    default_app = "dummy"
313
 
 
314
500
    # Get command-line arguments to avoid asking questions.
315
501
 
316
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
317
 
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
 
502
    optnames = []
 
503
    for opt in config_options:
 
504
        optnames.append(opt.option_name + "=")
 
505
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
318
506
 
319
507
    if args != []:
320
508
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
336
524
        # If EOF is encountered at any time during the questioning, just exit
337
525
        # silently
338
526
 
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
 
 
 
527
        for opt in config_options:
 
528
            globals()[opt.option_name] = \
 
529
                query_user(globals()[opt.option_name], opt.prompt)
353
530
    else:
354
531
        opts = dict(opts)
355
532
        # 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']
 
533
        for opt in config_options:
 
534
            if '--' + opt.option_name in opts:
 
535
                globals()[opt.option_name] = opts['--' + opt.option_name]
364
536
 
365
537
    # Error handling on input values
366
538
    try:
367
 
        allowed_uids = map(int, allowed_uids.split(','))
 
539
        allowed_uids_list = map(int, allowed_uids.split(','))
368
540
    except ValueError:
369
541
        print >>sys.stderr, (
370
542
        "Invalid UID list (%s).\n"
371
543
        "Must be a comma-separated list of integers." % allowed_uids)
372
544
        return 1
 
545
    try:
 
546
        db_port = int(db_port)
 
547
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
548
    except ValueError:
 
549
        print >>sys.stderr, (
 
550
        "Invalid DB port (%s).\n"
 
551
        "Must be an integer between 0 and 65535." % repr(db_port))
 
552
        return 1
373
553
 
374
554
    # Write www/conf/conf.py
375
555
 
380
560
# conf.py
381
561
# Miscellaneous application settings
382
562
 
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))
 
563
""")
 
564
        for opt in config_options:
 
565
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
566
                repr(globals()[opt.option_name])))
404
567
 
405
568
        conf.close()
406
569
    except IOError, (errno, strerror):
432
595
 * (Note that root is an implicit member of this list).
433
596
 */
434
597
static const int allowed_uids[] = { %s };
435
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
598
""" % (jail_base, repr(allowed_uids_list)[1:-1]))
436
599
 
437
600
        conf.close()
438
601
    except IOError, (errno, strerror):
458
621
        print "Dry run (no actions will be executed\n"
459
622
 
460
623
    # Compile the trampoline
461
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
462
 
        'trampoline/trampoline.c'], dry)
 
624
    curdir = os.getcwd()
 
625
    os.chdir('trampoline')
 
626
    action_runprog('make', [], dry)
 
627
    os.chdir(curdir)
463
628
 
464
629
    # Create the jail and its subdirectories
465
630
    # Note: Other subdirs will be made by copying files
470
635
    # Copy all console and operating system files into the jail
471
636
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
472
637
    copy_os_files_jail(dry)
 
638
    # Chmod the python console
 
639
    action_chmod_x('jail/opt/ivle/console/python-console', dry)
 
640
    
473
641
 
474
642
    # Compile .py files into .pyc or .pyo files
475
643
    compileall.compile_dir('www', quiet=True)
481
649
    """Copies necessary Operating System files from their usual locations
482
650
    into the jail/ directory of the cwd."""
483
651
    # 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)
 
652
    for filename in JAIL_FILES:
 
653
        copy_file_to_jail(filename, dry)
 
654
    for src, dst in JAIL_LINKS.items():
 
655
        action_symlink(src, dst, dry)
 
656
    for src, dst in JAIL_COPYTREES.items():
 
657
        action_copytree(src, dst, dry)
493
658
 
494
659
def copy_file_to_jail(src, dry):
495
660
    """Copies a single file from an absolute location into the same location
499
664
 
500
665
def install(args):
501
666
    # Get "dry" and "nojail" variables from command line
502
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
 
667
    (opts, args) = getopt.gnu_getopt(args, "n",
 
668
        ['dry', 'nojail', 'nosubjects'])
503
669
    opts = dict(opts)
504
670
    dry = '-n' in opts or '--dry' in opts
505
671
    nojail = '--nojail' in opts
 
672
    nosubjects = '--nosubjects' in opts
506
673
 
507
674
    if dry:
508
675
        print "Dry run (no actions will be executed\n"
530
697
        # to the jails template directory (it will be used as a template
531
698
        # for all the students' jails).
532
699
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
700
    if not nosubjects:
 
701
        # Copy the subjects and problems directories across
 
702
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
703
            srcdir="./subjects")
 
704
        action_copylist(install_list.list_problems, problems_base, dry,
 
705
            srcdir="./problems")
 
706
 
 
707
    # Append IVLE path to ivle.pth in python site packages
 
708
    # (Unless it's already there)
 
709
    ivle_pth = os.path.join(sys.prefix,
 
710
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
711
    ivle_www = os.path.join(ivle_install_dir, "www")
 
712
    write_ivle_pth = True
 
713
    try:
 
714
        file = open(ivle_pth, 'r')
 
715
        for line in file:
 
716
            if line.strip() == ivle_www:
 
717
                write_ivle_pth = False
 
718
                break
 
719
    except (IOError, OSError):
 
720
        pass
 
721
    if write_ivle_pth:
 
722
        action_append(ivle_pth, ivle_www)
 
723
 
 
724
    return 0
 
725
 
 
726
def updatejails(args):
 
727
    # Get "dry" variable from command line
 
728
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
729
    opts = dict(opts)
 
730
    dry = '-n' in opts or '--dry' in opts
 
731
 
 
732
    if dry:
 
733
        print "Dry run (no actions will be executed\n"
 
734
 
 
735
    if not dry and os.geteuid() != 0:
 
736
        print >>sys.stderr, "Must be root to run install"
 
737
        print >>sys.stderr, "(I need to chown some files)."
 
738
        return 1
 
739
 
 
740
    # Update the template jail directory in case it hasn't been installed
 
741
    # recently.
 
742
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
743
 
 
744
    # Re-link all the files in all students jails.
 
745
    for dir in os.listdir(jail_base):
 
746
        if dir == 'template': continue
 
747
        # First back up the student's home directory
 
748
        temp_home = os.tmpnam()
 
749
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
750
        # Delete the student's jail and relink the jail files
 
751
        action_linktree(os.path.join(jail_base, 'template'),
 
752
            os.path.join(jail_base, dir), dry)
 
753
        # Restore the student's home directory
 
754
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
755
        # Set up the user's home directory just in case they don't have a
 
756
        # directory for this yet
 
757
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
533
758
 
534
759
    return 0
535
760
 
562
787
    if ret != 0:
563
788
        raise RunError(prog, ret)
564
789
 
 
790
def action_rename(src, dst, dry):
 
791
    """Calls rename. Deletes the target if it already exists."""
 
792
    if os.access(dst, os.F_OK):
 
793
        print "rm -r", dst
 
794
        if not dry:
 
795
            shutil.rmtree(dst, True)
 
796
    print "mv ", src, dst
 
797
    if dry: return
 
798
    try:
 
799
        os.rename(src, dst)
 
800
    except OSError, (err, msg):
 
801
        if err != errno.EEXIST:
 
802
            raise
 
803
 
565
804
def action_mkdir(path, dry):
566
805
    """Calls mkdir. Silently ignored if the directory already exists.
567
806
    Creates all parent directories as necessary."""
587
826
    if dry: return
588
827
    shutil.copytree(src, dst, True)
589
828
 
590
 
def action_copylist(srclist, dst, dry):
 
829
def action_linktree(src, dst, dry):
 
830
    """Hard-links an entire directory tree. Same as copytree but the created
 
831
    files are hard-links not actual copies. Removes the existing destination.
 
832
    """
 
833
    if os.access(dst, os.F_OK):
 
834
        print "rm -r", dst
 
835
        if not dry:
 
836
            shutil.rmtree(dst, True)
 
837
    print "<cp with hardlinks> -r", src, dst
 
838
    if dry: return
 
839
    common.makeuser.linktree(src, dst)
 
840
 
 
841
def action_copylist(srclist, dst, dry, srcdir="."):
591
842
    """Copies all files in a list to a new location. The files in the list
592
843
    are read relative to the current directory, and their destinations are the
593
844
    same paths relative to dst. Creates all parent directories as necessary.
 
845
    srcdir is "." by default, can be overridden.
594
846
    """
595
847
    for srcfile in srclist:
596
848
        dstfile = os.path.join(dst, srcfile)
 
849
        srcfile = os.path.join(srcdir, srcfile)
597
850
        dstdir = os.path.split(dstfile)[0]
598
851
        if not os.path.isdir(dstdir):
599
852
            action_mkdir(dstdir, dry)
608
861
def action_copyfile(src, dst, dry):
609
862
    """Copies one file to a new location. Creates all parent directories
610
863
    as necessary.
 
864
    Warn if file not found.
611
865
    """
612
866
    dstdir = os.path.split(dst)[0]
613
867
    if not os.path.isdir(dstdir):
617
871
        try:
618
872
            shutil.copyfile(src, dst)
619
873
            shutil.copymode(src, dst)
620
 
        except shutil.Error:
621
 
            pass
 
874
        except (shutil.Error, IOError), e:
 
875
            print "Warning: " + str(e)
622
876
 
623
877
def action_symlink(src, dst, dry):
624
878
    """Creates a symlink in a given location. Creates all parent directories
634
888
    if not dry:
635
889
        os.symlink(src, dst)
636
890
 
 
891
def action_append(ivle_pth, ivle_www):
 
892
    file = open(ivle_pth, 'a+')
 
893
    file.write(ivle_www + '\n')
 
894
    file.close()
 
895
 
637
896
def action_chown_setuid(file, dry):
638
897
    """Chowns a file to root, and sets the setuid bit on the file.
639
898
    Calling this function requires the euid to be root.
648
907
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
649
908
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
650
909
 
 
910
def action_chmod_x(file, dry):
 
911
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
912
    print "chmod 755", file
 
913
    if not dry:
 
914
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
915
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
916
 
651
917
def query_user(default, prompt):
652
918
    """Prompts the user for a string, which is read from a line of stdin.
653
919
    Exits silently if EOF is encountered. Returns the string, with spaces