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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-02-05 03:04:05 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:413
fileservice: Accidentally forgot to add __init__.py. (So last commit was an
    empty directory).

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')
 
405
    list_lib = build_list_py_files('lib')
234
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)
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_www.append("lib/conf/conf.py")
239
413
    # Make sure that console/python-console is in the list
240
414
    if "console/python-console" not in list_console:
241
415
        list_console.append("console/python-console")
258
432
list_www = """)
259
433
        writelist_pretty(file, list_www)
260
434
        file.write("""
 
435
# List of all installable files in lib directory.
 
436
list_lib = """)
 
437
        writelist_pretty(file, list_lib)
 
438
        file.write("""
261
439
# List of all installable files in console directory.
262
440
list_console = """)
263
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)
264
452
 
265
453
        file.close()
266
454
    except IOError, (errno, strerror):
277
465
 
278
466
    return 0
279
467
 
280
 
def build_list_py_files(dir):
 
468
def build_list_py_files(dir, no_top_level=False):
281
469
    """Builds a list of all py files found in a directory and its
282
 
    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
    """
283
474
    pylist = []
284
475
    for (dirpath, dirnames, filenames) in os.walk(dir):
285
476
        # Exclude directories beginning with a '.' (such as '.svn')
287
478
        # All *.py files are added to the list
288
479
        pylist += [os.path.join(dirpath, item) for item in filenames
289
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)
290
484
    return pylist
291
485
 
292
486
def writelist_pretty(file, list):
300
494
        file.write(']\n')
301
495
 
302
496
def conf(args):
303
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
497
    global db_port
304
498
    # Set up some variables
305
499
 
306
500
    cwd = os.getcwd()
307
501
    # the files that will be created/overwritten
308
 
    conffile = os.path.join(cwd, "www/conf/conf.py")
 
502
    conffile = os.path.join(cwd, "lib/conf/conf.py")
309
503
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
310
504
 
311
 
    # Fixed config options that we don't ask the admin
312
 
    default_app = "dummy"
313
 
 
314
505
    # Get command-line arguments to avoid asking questions.
315
506
 
316
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
317
 
                    '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)
318
511
 
319
512
    if args != []:
320
513
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
336
529
        # If EOF is encountered at any time during the questioning, just exit
337
530
        # silently
338
531
 
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
 
 
 
532
        for opt in config_options:
 
533
            globals()[opt.option_name] = \
 
534
                query_user(globals()[opt.option_name], opt.prompt)
353
535
    else:
354
536
        opts = dict(opts)
355
537
        # 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']
 
538
        for opt in config_options:
 
539
            if '--' + opt.option_name in opts:
 
540
                globals()[opt.option_name] = opts['--' + opt.option_name]
364
541
 
365
542
    # Error handling on input values
366
543
    try:
367
 
        allowed_uids = map(int, allowed_uids.split(','))
 
544
        allowed_uids_list = map(int, allowed_uids.split(','))
368
545
    except ValueError:
369
546
        print >>sys.stderr, (
370
547
        "Invalid UID list (%s).\n"
371
548
        "Must be a comma-separated list of integers." % allowed_uids)
372
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
373
558
 
374
 
    # Write www/conf/conf.py
 
559
    # Write lib/conf/conf.py
375
560
 
376
561
    try:
377
562
        conf = open(conffile, "w")
380
565
# conf.py
381
566
# Miscellaneous application settings
382
567
 
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))
 
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