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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-02-05 01:51:26 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:411
Renamed lib/fileservice to lib/fileservice_lib (naming conflict).
Added new app: fileservice. This app replaces the old one that was deleted -
it simply calls fileservice_lib.handle, and that's it (so it functions exactly
the same).

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
 
    try:
78
 
        root_dir = confmodule.root_dir
79
 
    except:
80
 
        root_dir = "/ivle"
81
 
    try:
82
 
        ivle_install_dir = confmodule.ivle_install_dir
83
 
    except:
84
 
        ivle_install_dir = "/opt/ivle"
85
 
    try:
86
 
        jail_base = confmodule.jail_base
87
 
    except:
88
 
        jail_base = "/home/informatics/jails"
 
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
89
246
except ImportError:
90
247
    # Just set reasonable defaults
91
 
    root_dir = "/ivle"
92
 
    ivle_install_dir = "/opt/ivle"
93
 
    jail_base = "/home/informatics/jails"
94
 
# Always defaults
95
 
allowed_uids = "0"
 
248
    for opt in config_options:
 
249
        globals()[opt.option_name] = opt.default
96
250
 
97
251
# Try importing install_list, but don't fail if we can't, because listmake can
98
252
# function without it.
107
261
# as necessary, and include it in the distribution.
108
262
listmake_mimetypes = ['text/x-python', 'text/html',
109
263
    'application/x-javascript', 'application/javascript',
110
 
    'text/css', 'image/png']
 
264
    'text/css', 'image/png', 'application/xml']
111
265
 
112
266
# Main function skeleton from Guido van Rossum
113
267
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
136
290
        return 1
137
291
 
138
292
    # Disallow run as root unless installing
139
 
    if operation != 'install' and os.geteuid() == 0:
 
293
    if (operation != 'install' and operation != 'updatejails'
 
294
        and os.geteuid() == 0):
140
295
        print >>sys.stderr, "I do not want to run this stage as root."
141
296
        print >>sys.stderr, "Please run as a normal user."
142
297
        return 1
144
299
    try:
145
300
        oper_func = {
146
301
            'help' : help,
147
 
            'conf' : conf,
 
302
            'config' : conf,
148
303
            'build' : build,
149
304
            'listmake' : listmake,
150
305
            'install' : install,
 
306
            'updatejails' : updatejails,
151
307
        }[operation]
152
308
    except KeyError:
153
309
        print >>sys.stderr, (
164
320
Operation (and args) can be:
165
321
    help [operation]
166
322
    listmake (developer use only)
167
 
    conf [args]
 
323
    config [args]
168
324
    build
169
 
    install [--nojail] [-n|--dry]
 
325
    install [--nojail] [--nosubjects] [-n|--dry]
170
326
"""
171
327
        return 1
172
328
    elif len(args) != 1:
185
341
be copied upon installation. This should be run by the developer before
186
342
cutting a distribution, and the listfile it generates should be included in
187
343
the distribution, avoiding the administrator having to run it."""
188
 
    elif operation == 'conf':
189
 
        print """python setup.py conf [args]
 
344
    elif operation == 'config':
 
345
        print """python setup.py config [args]
190
346
Configures IVLE with machine-specific details, most notably, various paths.
191
347
Either prompts the administrator for these details or accepts them as
192
348
command-line args. Will be interactive only if there are no arguments given.
196
352
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
197
353
directory, and run build/install one time.
198
354
 
199
 
Creates www/conf/conf.py and trampoline/conf.h.
 
355
Creates lib/conf/conf.py and trampoline/conf.h.
200
356
 
201
 
Args are:
202
 
    --root_dir
203
 
    --ivle_install_dir
204
 
    --jail_base
205
 
    --allowed_uids
206
 
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.
207
361
"""
208
362
    elif operation == 'build':
209
363
        print """python -O setup.py build [--dry|-n]
220
374
 
221
375
--dry | -n  Print out the actions but don't do anything."""
222
376
    elif operation == 'install':
223
 
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
377
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
224
378
(Requires root)
225
379
Create target install directory ($target).
226
380
Create $target/bin.
228
382
chown and chmod the installed trampoline.
229
383
Copy www/ to $target.
230
384
Copy jail/ to jails template directory (unless --nojail specified).
231
 
 
232
 
--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
 
233
395
--dry | -n  Print out the actions but don't do anything."""
234
396
    else:
235
397
        print >>sys.stderr, (
240
402
def listmake(args):
241
403
    # We build two separate lists, by walking www and console
242
404
    list_www = build_list_py_files('www')
 
405
    list_lib = build_list_py_files('lib')
243
406
    list_console = build_list_py_files('console')
 
407
    list_subjects = build_list_py_files('subjects', no_top_level=True)
 
408
    list_problems = build_list_py_files('problems', no_top_level=True)
244
409
    # Make sure that the files generated by conf are in the list
245
410
    # (since listmake is typically run before conf)
246
 
    if "www/conf/conf.py" not in list_www:
247
 
        list_www.append("www/conf/conf.py")
 
411
    if "lib/conf/conf.py" not in list_lib:
 
412
        list_www.append("lib/conf/conf.py")
248
413
    # Make sure that console/python-console is in the list
249
414
    if "console/python-console" not in list_console:
250
415
        list_console.append("console/python-console")
267
432
list_www = """)
268
433
        writelist_pretty(file, list_www)
269
434
        file.write("""
 
435
# List of all installable files in lib directory.
 
436
list_lib = """)
 
437
        writelist_pretty(file, list_lib)
 
438
        file.write("""
270
439
# List of all installable files in console directory.
271
440
list_console = """)
272
441
        writelist_pretty(file, list_console)
 
442
        file.write("""
 
443
# List of all installable files in subjects directory.
 
444
# This is to install sample subjects and material.
 
445
list_subjects = """)
 
446
        writelist_pretty(file, list_subjects)
 
447
        file.write("""
 
448
# List of all installable files in problems directory.
 
449
# This is to install sample exercise material.
 
450
list_problems = """)
 
451
        writelist_pretty(file, list_problems)
273
452
 
274
453
        file.close()
275
454
    except IOError, (errno, strerror):
286
465
 
287
466
    return 0
288
467
 
289
 
def build_list_py_files(dir):
 
468
def build_list_py_files(dir, no_top_level=False):
290
469
    """Builds a list of all py files found in a directory and its
291
 
    subdirectories. Returns this as a list of strings."""
 
470
    subdirectories. Returns this as a list of strings.
 
471
    no_top_level=True means the file paths will not include the top-level
 
472
    directory.
 
473
    """
292
474
    pylist = []
293
475
    for (dirpath, dirnames, filenames) in os.walk(dir):
294
476
        # Exclude directories beginning with a '.' (such as '.svn')
296
478
        # All *.py files are added to the list
297
479
        pylist += [os.path.join(dirpath, item) for item in filenames
298
480
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
481
    if no_top_level:
 
482
        for i in range(0, len(pylist)):
 
483
            _, pylist[i] = pylist[i].split(os.sep, 1)
299
484
    return pylist
300
485
 
301
486
def writelist_pretty(file, list):
309
494
        file.write(']\n')
310
495
 
311
496
def conf(args):
312
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
497
    global db_port
313
498
    # Set up some variables
314
499
 
315
500
    cwd = os.getcwd()
316
501
    # the files that will be created/overwritten
317
 
    conffile = os.path.join(cwd, "www/conf/conf.py")
 
502
    conffile = os.path.join(cwd, "lib/conf/conf.py")
318
503
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
319
504
 
320
505
    # Get command-line arguments to avoid asking questions.
321
506
 
322
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
323
 
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
 
507
    optnames = []
 
508
    for opt in config_options:
 
509
        optnames.append(opt.option_name + "=")
 
510
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
324
511
 
325
512
    if args != []:
326
513
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
342
529
        # If EOF is encountered at any time during the questioning, just exit
343
530
        # silently
344
531
 
345
 
        root_dir = query_user(root_dir,
346
 
        """Root directory where IVLE is located (in URL space):""")
347
 
        ivle_install_dir = query_user(ivle_install_dir,
348
 
        'Root directory where IVLE will be installed (on the local file '
349
 
        'system):')
350
 
        jail_base = query_user(jail_base,
351
 
        """Root directory where the jails (containing user files) are stored
352
 
(on the local file system):""")
353
 
        allowed_uids = query_user(allowed_uids,
354
 
        """UID of the web server process which will run IVLE.
355
 
Only this user may execute the trampoline. May specify multiple users as
356
 
a comma-separated list.
357
 
    (eg. "1002,78")""")
358
 
 
 
532
        for opt in config_options:
 
533
            globals()[opt.option_name] = \
 
534
                query_user(globals()[opt.option_name], opt.prompt)
359
535
    else:
360
536
        opts = dict(opts)
361
537
        # Non-interactive mode. Parse the options.
362
 
        if '--root_dir' in opts:
363
 
            root_dir = opts['--root_dir']
364
 
        if '--ivle_install_dir' in opts:
365
 
            ivle_install_dir = opts['--ivle_install_dir']
366
 
        if '--jail_base' in opts:
367
 
            jail_base = opts['--jail_base']
368
 
        if '--allowed_uids' in opts:
369
 
            allowed_uids = opts['--allowed_uids']
 
538
        for opt in config_options:
 
539
            if '--' + opt.option_name in opts:
 
540
                globals()[opt.option_name] = opts['--' + opt.option_name]
370
541
 
371
542
    # Error handling on input values
372
543
    try:
373
 
        allowed_uids = map(int, allowed_uids.split(','))
 
544
        allowed_uids_list = map(int, allowed_uids.split(','))
374
545
    except ValueError:
375
546
        print >>sys.stderr, (
376
547
        "Invalid UID list (%s).\n"
377
548
        "Must be a comma-separated list of integers." % allowed_uids)
378
549
        return 1
 
550
    try:
 
551
        db_port = int(db_port)
 
552
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
553
    except ValueError:
 
554
        print >>sys.stderr, (
 
555
        "Invalid DB port (%s).\n"
 
556
        "Must be an integer between 0 and 65535." % repr(db_port))
 
557
        return 1
379
558
 
380
 
    # Write www/conf/conf.py
 
559
    # Write lib/conf/conf.py
381
560
 
382
561
    try:
383
562
        conf = open(conffile, "w")
386
565
# conf.py
387
566
# Miscellaneous application settings
388
567
 
389
 
 
390
 
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
391
 
# with this).
392
 
# eg. "/" or "/ivle".
393
 
root_dir = "%s"
394
 
 
395
 
# In the local file system, where IVLE is actually installed.
396
 
# This directory should contain the "www" and "bin" directories.
397
 
ivle_install_dir = "%s"
398
 
 
399
 
# In the local file system, where are the student/user file spaces located.
400
 
# The user jails are expected to be located immediately in subdirectories of
401
 
# this location.
402
 
jail_base = "%s"
403
 
""" % (root_dir, ivle_install_dir, jail_base))
 
568
""")
 
569
        for opt in config_options:
 
570
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
571
                repr(globals()[opt.option_name])))
404
572
 
405
573
        conf.close()
406
574
    except IOError, (errno, strerror):
407
575
        print "IO error(%s): %s" % (errno, strerror)
408
576
        sys.exit(1)
409
577
 
410
 
    print "Successfully wrote www/conf/conf.py"
 
578
    print "Successfully wrote lib/conf/conf.py"
411
579
 
412
580
    # Write trampoline/conf.h
413
581
 
432
600
 * (Note that root is an implicit member of this list).
433
601
 */
434
602
static const int allowed_uids[] = { %s };
435
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
603
""" % (jail_base, repr(allowed_uids_list)[1:-1]))
436
604
 
437
605
        conf.close()
438
606
    except IOError, (errno, strerror):
458
626
        print "Dry run (no actions will be executed\n"
459
627
 
460
628
    # Compile the trampoline
461
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
462
 
        'trampoline/trampoline.c'], dry)
 
629
    curdir = os.getcwd()
 
630
    os.chdir('trampoline')
 
631
    action_runprog('make', [], dry)
 
632
    os.chdir(curdir)
463
633
 
464
634
    # Create the jail and its subdirectories
465
635
    # Note: Other subdirs will be made by copying files
470
640
    # Copy all console and operating system files into the jail
471
641
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
472
642
    copy_os_files_jail(dry)
 
643
    # Chmod the python console
 
644
    action_chmod_x('jail/opt/ivle/console/python-console', dry)
 
645
    
473
646
 
474
647
    # Compile .py files into .pyc or .pyo files
475
648
    compileall.compile_dir('www', quiet=True)
481
654
    """Copies necessary Operating System files from their usual locations
482
655
    into the jail/ directory of the cwd."""
483
656
    # 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)
 
657
    for filename in JAIL_FILES:
 
658
        copy_file_to_jail(filename, dry)
 
659
    for src, dst in JAIL_LINKS.items():
 
660
        action_symlink(src, dst, dry)
 
661
    for src, dst in JAIL_COPYTREES.items():
 
662
        action_copytree(src, dst, dry)
493
663
 
494
664
def copy_file_to_jail(src, dry):
495
665
    """Copies a single file from an absolute location into the same location
499
669
 
500
670
def install(args):
501
671
    # Get "dry" and "nojail" variables from command line
502
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
 
672
    (opts, args) = getopt.gnu_getopt(args, "n",
 
673
        ['dry', 'nojail', 'nosubjects'])
503
674
    opts = dict(opts)
504
675
    dry = '-n' in opts or '--dry' in opts
505
676
    nojail = '--nojail' in opts
 
677
    nosubjects = '--nosubjects' in opts
506
678
 
507
679
    if dry:
508
680
        print "Dry run (no actions will be executed\n"
522
694
    # chown trampoline to root and set setuid bit
523
695
    action_chown_setuid(tramppath, dry)
524
696
 
525
 
    # Copy the www directory using the list
 
697
    # Copy the www and lib directories using the list
526
698
    action_copylist(install_list.list_www, ivle_install_dir, dry)
 
699
    action_copylist(install_list.list_lib, ivle_install_dir, dry)
527
700
 
528
701
    if not nojail:
529
702
        # Copy the local jail directory built by the build action
530
703
        # to the jails template directory (it will be used as a template
531
704
        # for all the students' jails).
532
705
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
706
    if not nosubjects:
 
707
        # Copy the subjects and problems directories across
 
708
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
709
            srcdir="./subjects")
 
710
        action_copylist(install_list.list_problems, problems_base, dry,
 
711
            srcdir="./problems")
 
712
 
 
713
    # Append IVLE path to ivle.pth in python site packages
 
714
    # (Unless it's already there)
 
715
    ivle_pth = os.path.join(sys.prefix,
 
716
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
717
    ivle_www = os.path.join(ivle_install_dir, "www")
 
718
    ivle_lib = os.path.join(ivle_install_dir, "lib")
 
719
    write_ivle_pth = True
 
720
    write_ivle_lib_pth = True
 
721
    try:
 
722
        file = open(ivle_pth, 'r')
 
723
        for line in file:
 
724
            if line.strip() == ivle_www:
 
725
                write_ivle_pth = False
 
726
            elif line.strip() == ivle_lib:
 
727
                write_ivle_lib_pth = False
 
728
        file.close()
 
729
    except (IOError, OSError):
 
730
        pass
 
731
    if write_ivle_pth:
 
732
        action_append(ivle_pth, ivle_www)
 
733
    if write_ivle_lib_pth:
 
734
        action_append(ivle_pth, ivle_lib)
 
735
 
 
736
    return 0
 
737
 
 
738
def updatejails(args):
 
739
    # Get "dry" variable from command line
 
740
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
741
    opts = dict(opts)
 
742
    dry = '-n' in opts or '--dry' in opts
 
743
 
 
744
    if dry:
 
745
        print "Dry run (no actions will be executed\n"
 
746
 
 
747
    if not dry and os.geteuid() != 0:
 
748
        print >>sys.stderr, "Must be root to run install"
 
749
        print >>sys.stderr, "(I need to chown some files)."
 
750
        return 1
 
751
 
 
752
    # Update the template jail directory in case it hasn't been installed
 
753
    # recently.
 
754
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
755
 
 
756
    # Re-link all the files in all students jails.
 
757
    for dir in os.listdir(jail_base):
 
758
        if dir == 'template': continue
 
759
        # First back up the student's home directory
 
760
        temp_home = os.tmpnam()
 
761
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
762
        # Delete the student's jail and relink the jail files
 
763
        action_linktree(os.path.join(jail_base, 'template'),
 
764
            os.path.join(jail_base, dir), dry)
 
765
        # Restore the student's home directory
 
766
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
767
        # Set up the user's home directory just in case they don't have a
 
768
        # directory for this yet
 
769
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
533
770
 
534
771
    return 0
535
772
 
562
799
    if ret != 0:
563
800
        raise RunError(prog, ret)
564
801
 
 
802
def action_rename(src, dst, dry):
 
803
    """Calls rename. Deletes the target if it already exists."""
 
804
    if os.access(dst, os.F_OK):
 
805
        print "rm -r", dst
 
806
        if not dry:
 
807
            shutil.rmtree(dst, True)
 
808
    print "mv ", src, dst
 
809
    if dry: return
 
810
    try:
 
811
        os.rename(src, dst)
 
812
    except OSError, (err, msg):
 
813
        if err != errno.EEXIST:
 
814
            raise
 
815
 
565
816
def action_mkdir(path, dry):
566
817
    """Calls mkdir. Silently ignored if the directory already exists.
567
818
    Creates all parent directories as necessary."""
587
838
    if dry: return
588
839
    shutil.copytree(src, dst, True)
589
840
 
590
 
def action_copylist(srclist, dst, dry):
 
841
def action_linktree(src, dst, dry):
 
842
    """Hard-links an entire directory tree. Same as copytree but the created
 
843
    files are hard-links not actual copies. Removes the existing destination.
 
844
    """
 
845
    if os.access(dst, os.F_OK):
 
846
        print "rm -r", dst
 
847
        if not dry:
 
848
            shutil.rmtree(dst, True)
 
849
    print "<cp with hardlinks> -r", src, dst
 
850
    if dry: return
 
851
    common.makeuser.linktree(src, dst)
 
852
 
 
853
def action_copylist(srclist, dst, dry, srcdir="."):
591
854
    """Copies all files in a list to a new location. The files in the list
592
855
    are read relative to the current directory, and their destinations are the
593
856
    same paths relative to dst. Creates all parent directories as necessary.
 
857
    srcdir is "." by default, can be overridden.
594
858
    """
595
859
    for srcfile in srclist:
596
860
        dstfile = os.path.join(dst, srcfile)
 
861
        srcfile = os.path.join(srcdir, srcfile)
597
862
        dstdir = os.path.split(dstfile)[0]
598
863
        if not os.path.isdir(dstdir):
599
864
            action_mkdir(dstdir, dry)
608
873
def action_copyfile(src, dst, dry):
609
874
    """Copies one file to a new location. Creates all parent directories
610
875
    as necessary.
 
876
    Warn if file not found.
611
877
    """
612
878
    dstdir = os.path.split(dst)[0]
613
879
    if not os.path.isdir(dstdir):
617
883
        try:
618
884
            shutil.copyfile(src, dst)
619
885
            shutil.copymode(src, dst)
620
 
        except shutil.Error:
621
 
            pass
 
886
        except (shutil.Error, IOError), e:
 
887
            print "Warning: " + str(e)
622
888
 
623
889
def action_symlink(src, dst, dry):
624
890
    """Creates a symlink in a given location. Creates all parent directories
634
900
    if not dry:
635
901
        os.symlink(src, dst)
636
902
 
 
903
def action_append(ivle_pth, ivle_www):
 
904
    file = open(ivle_pth, 'a+')
 
905
    file.write(ivle_www + '\n')
 
906
    file.close()
 
907
 
637
908
def action_chown_setuid(file, dry):
638
909
    """Chowns a file to root, and sets the setuid bit on the file.
639
910
    Calling this function requires the euid to be root.
648
919
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
649
920
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
650
921
 
 
922
def action_chmod_x(file, dry):
 
923
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
924
    print "chmod 755", file
 
925
    if not dry:
 
926
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
927
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
928
 
651
929
def query_user(default, prompt):
652
930
    """Prompts the user for a string, which is read from a line of stdin.
653
931
    Exits silently if EOF is encountered. Returns the string, with spaces