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

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: stevenbird
  • Date: 2008-02-01 03:51:56 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:368
First version of a DTD for XML problem files

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("public_host", "public.localhost",
 
183
    """Hostname which will cause the server to go into "public mode",
 
184
providing login-free access to student's published work:""",
 
185
    """
 
186
# The server goes into "public mode" if the browser sends a request with this
 
187
# host. This is for security reasons - we only serve public student files on a
 
188
# separate domain to the main IVLE site.
 
189
# Public mode does not use cookies, and serves only public content.
 
190
# Private mode (normal mode) requires login, and only serves files relevant to
 
191
# the logged-in user."""))
 
192
config_options.append(ConfigOption("allowed_uids", "33",
 
193
    """UID of the web server process which will run IVLE.
 
194
Only this user may execute the trampoline. May specify multiple users as
 
195
a comma-separated list.
 
196
    (eg. "1002,78")""",
 
197
    """
 
198
# The User-ID of the web server process which will run IVLE, and any other
 
199
# users who are allowed to run the trampoline. This is stores as a string of
 
200
# comma-separated integers, simply because it is not used within Python, only
 
201
# used by the setup program to write to conf.h (see setup.py config)."""))
 
202
config_options.append(ConfigOption("db_host", "localhost",
 
203
    """PostgreSQL Database config
 
204
==========================
 
205
Hostname of the DB server:""",
 
206
    """
 
207
### PostgreSQL Database config ###
 
208
# Database server hostname"""))
 
209
config_options.append(ConfigOption("db_port", "5432",
 
210
    """Port of the DB server:""",
 
211
    """
 
212
# Database server port"""))
 
213
config_options.append(ConfigOption("db_dbname", "ivle",
 
214
    """Database name:""",
 
215
    """
 
216
# Database name"""))
 
217
config_options.append(ConfigOption("db_user", "postgres",
 
218
    """Username for DB server login:""",
 
219
    """
 
220
# Database username"""))
 
221
config_options.append(ConfigOption("db_password", "",
 
222
    """Password for DB server login:
 
223
    (Caution: This password is stored in plaintext in www/conf/conf.py)""",
 
224
    """
 
225
# Database password"""))
 
226
 
71
227
# Try importing existing conf, but if we can't just set up defaults
72
228
# The reason for this is that these settings are used by other phases
73
229
# of setup besides conf, so we need to know them.
74
230
# Also this allows you to hit Return to accept the existing value.
75
231
try:
76
232
    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
 
233
    for opt in config_options:
 
234
        try:
 
235
            globals()[opt.option_name] = confmodule.__dict__[opt.option_name]
 
236
        except:
 
237
            globals()[opt.option_name] = opt.default
80
238
except ImportError:
81
239
    # 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"
 
240
    for opt in config_options:
 
241
        globals()[opt.option_name] = opt.default
87
242
 
88
243
# Try importing install_list, but don't fail if we can't, because listmake can
89
244
# function without it.
98
253
# as necessary, and include it in the distribution.
99
254
listmake_mimetypes = ['text/x-python', 'text/html',
100
255
    'application/x-javascript', 'application/javascript',
101
 
    'text/css', 'image/png']
 
256
    'text/css', 'image/png', 'application/xml']
102
257
 
103
258
# Main function skeleton from Guido van Rossum
104
259
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
127
282
        return 1
128
283
 
129
284
    # Disallow run as root unless installing
130
 
    if operation != 'install' and os.geteuid() == 0:
 
285
    if (operation != 'install' and operation != 'updatejails'
 
286
        and os.geteuid() == 0):
131
287
        print >>sys.stderr, "I do not want to run this stage as root."
132
288
        print >>sys.stderr, "Please run as a normal user."
133
289
        return 1
135
291
    try:
136
292
        oper_func = {
137
293
            'help' : help,
138
 
            'conf' : conf,
 
294
            'config' : conf,
139
295
            'build' : build,
140
296
            'listmake' : listmake,
141
297
            'install' : install,
 
298
            'updatejails' : updatejails,
142
299
        }[operation]
143
300
    except KeyError:
144
301
        print >>sys.stderr, (
155
312
Operation (and args) can be:
156
313
    help [operation]
157
314
    listmake (developer use only)
158
 
    conf [args]
 
315
    config [args]
159
316
    build
160
 
    install [--nojail] [-n|--dry]
 
317
    install [--nojail] [--nosubjects] [-n|--dry]
161
318
"""
162
319
        return 1
163
320
    elif len(args) != 1:
176
333
be copied upon installation. This should be run by the developer before
177
334
cutting a distribution, and the listfile it generates should be included in
178
335
the distribution, avoiding the administrator having to run it."""
179
 
    elif operation == 'conf':
180
 
        print """python setup.py conf [args]
 
336
    elif operation == 'config':
 
337
        print """python setup.py config [args]
181
338
Configures IVLE with machine-specific details, most notably, various paths.
182
339
Either prompts the administrator for these details or accepts them as
183
340
command-line args. Will be interactive only if there are no arguments given.
189
346
 
190
347
Creates www/conf/conf.py and trampoline/conf.h.
191
348
 
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.
 
349
Args are:"""
 
350
        for opt in config_options:
 
351
            print "    --" + opt.option_name
 
352
        print """As explained in the interactive prompt or conf.py.
198
353
"""
199
354
    elif operation == 'build':
200
355
        print """python -O setup.py build [--dry|-n]
211
366
 
212
367
--dry | -n  Print out the actions but don't do anything."""
213
368
    elif operation == 'install':
214
 
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
369
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
215
370
(Requires root)
216
371
Create target install directory ($target).
217
372
Create $target/bin.
219
374
chown and chmod the installed trampoline.
220
375
Copy www/ to $target.
221
376
Copy jail/ to jails template directory (unless --nojail specified).
222
 
 
223
 
--nojail    Do not copy the jail.
 
377
Copy subjects/ to subjects directory (unless --nosubjects specified).
 
378
 
 
379
--nojail        Do not copy the jail.
 
380
--nosubjects    Do not copy the subjects.
 
381
--dry | -n  Print out the actions but don't do anything."""
 
382
    elif operation == 'updatejails':
 
383
        print """sudo python setup.py updatejails [--dry|-n]
 
384
(Requires root)
 
385
Copy jail/ to each subdirectory in jails directory.
 
386
 
224
387
--dry | -n  Print out the actions but don't do anything."""
225
388
    else:
226
389
        print >>sys.stderr, (
232
395
    # We build two separate lists, by walking www and console
233
396
    list_www = build_list_py_files('www')
234
397
    list_console = build_list_py_files('console')
 
398
    list_subjects = build_list_py_files('subjects', no_top_level=True)
235
399
    # Make sure that the files generated by conf are in the list
236
400
    # (since listmake is typically run before conf)
237
401
    if "www/conf/conf.py" not in list_www:
261
425
# List of all installable files in console directory.
262
426
list_console = """)
263
427
        writelist_pretty(file, list_console)
 
428
        file.write("""
 
429
# List of all installable files in subjects directory.
 
430
# This is to install sample subjects and material.
 
431
list_subjects = """)
 
432
        writelist_pretty(file, list_subjects)
264
433
 
265
434
        file.close()
266
435
    except IOError, (errno, strerror):
277
446
 
278
447
    return 0
279
448
 
280
 
def build_list_py_files(dir):
 
449
def build_list_py_files(dir, no_top_level=False):
281
450
    """Builds a list of all py files found in a directory and its
282
 
    subdirectories. Returns this as a list of strings."""
 
451
    subdirectories. Returns this as a list of strings.
 
452
    no_top_level=True means the file paths will not include the top-level
 
453
    directory.
 
454
    """
283
455
    pylist = []
284
456
    for (dirpath, dirnames, filenames) in os.walk(dir):
285
457
        # Exclude directories beginning with a '.' (such as '.svn')
287
459
        # All *.py files are added to the list
288
460
        pylist += [os.path.join(dirpath, item) for item in filenames
289
461
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
462
    if no_top_level:
 
463
        for i in range(0, len(pylist)):
 
464
            _, pylist[i] = pylist[i].split(os.sep, 1)
290
465
    return pylist
291
466
 
292
467
def writelist_pretty(file, list):
300
475
        file.write(']\n')
301
476
 
302
477
def conf(args):
303
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
478
    global db_port
304
479
    # Set up some variables
305
480
 
306
481
    cwd = os.getcwd()
308
483
    conffile = os.path.join(cwd, "www/conf/conf.py")
309
484
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
310
485
 
311
 
    # Fixed config options that we don't ask the admin
312
 
    default_app = "dummy"
313
 
 
314
486
    # Get command-line arguments to avoid asking questions.
315
487
 
316
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
317
 
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
 
488
    optnames = []
 
489
    for opt in config_options:
 
490
        optnames.append(opt.option_name + "=")
 
491
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
318
492
 
319
493
    if args != []:
320
494
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
336
510
        # If EOF is encountered at any time during the questioning, just exit
337
511
        # silently
338
512
 
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
 
 
 
513
        for opt in config_options:
 
514
            globals()[opt.option_name] = \
 
515
                query_user(globals()[opt.option_name], opt.prompt)
353
516
    else:
354
517
        opts = dict(opts)
355
518
        # 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']
 
519
        for opt in config_options:
 
520
            if '--' + opt.option_name in opts:
 
521
                globals()[opt.option_name] = opts['--' + opt.option_name]
364
522
 
365
523
    # Error handling on input values
366
524
    try:
367
 
        allowed_uids = map(int, allowed_uids.split(','))
 
525
        allowed_uids_list = map(int, allowed_uids.split(','))
368
526
    except ValueError:
369
527
        print >>sys.stderr, (
370
528
        "Invalid UID list (%s).\n"
371
529
        "Must be a comma-separated list of integers." % allowed_uids)
372
530
        return 1
 
531
    try:
 
532
        db_port = int(db_port)
 
533
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
534
    except ValueError:
 
535
        print >>sys.stderr, (
 
536
        "Invalid DB port (%s).\n"
 
537
        "Must be an integer between 0 and 65535." % repr(db_port))
 
538
        return 1
373
539
 
374
540
    # Write www/conf/conf.py
375
541
 
380
546
# conf.py
381
547
# Miscellaneous application settings
382
548
 
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))
 
549
""")
 
550
        for opt in config_options:
 
551
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
552
                repr(globals()[opt.option_name])))
404
553
 
405
554
        conf.close()
406
555
    except IOError, (errno, strerror):
432
581
 * (Note that root is an implicit member of this list).
433
582
 */
434
583
static const int allowed_uids[] = { %s };
435
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
584
""" % (jail_base, repr(allowed_uids_list)[1:-1]))
436
585
 
437
586
        conf.close()
438
587
    except IOError, (errno, strerror):
458
607
        print "Dry run (no actions will be executed\n"
459
608
 
460
609
    # Compile the trampoline
461
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
462
 
        'trampoline/trampoline.c'], dry)
 
610
    curdir = os.getcwd()
 
611
    os.chdir('trampoline')
 
612
    action_runprog('make', [], dry)
 
613
    os.chdir(curdir)
463
614
 
464
615
    # Create the jail and its subdirectories
465
616
    # Note: Other subdirs will be made by copying files
470
621
    # Copy all console and operating system files into the jail
471
622
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
472
623
    copy_os_files_jail(dry)
 
624
    # Chmod the python console
 
625
    action_chmod_x('jail/opt/ivle/console/python-console', dry)
 
626
    
473
627
 
474
628
    # Compile .py files into .pyc or .pyo files
475
629
    compileall.compile_dir('www', quiet=True)
481
635
    """Copies necessary Operating System files from their usual locations
482
636
    into the jail/ directory of the cwd."""
483
637
    # 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)
 
638
    for filename in JAIL_FILES:
 
639
        copy_file_to_jail(filename, dry)
 
640
    for src, dst in JAIL_LINKS.items():
 
641
        action_symlink(src, dst, dry)
 
642
    for src, dst in JAIL_COPYTREES.items():
 
643
        action_copytree(src, dst, dry)
493
644
 
494
645
def copy_file_to_jail(src, dry):
495
646
    """Copies a single file from an absolute location into the same location
499
650
 
500
651
def install(args):
501
652
    # Get "dry" and "nojail" variables from command line
502
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
 
653
    (opts, args) = getopt.gnu_getopt(args, "n",
 
654
        ['dry', 'nojail', 'nosubjects'])
503
655
    opts = dict(opts)
504
656
    dry = '-n' in opts or '--dry' in opts
505
657
    nojail = '--nojail' in opts
 
658
    nosubjects = '--nosubjects' in opts
506
659
 
507
660
    if dry:
508
661
        print "Dry run (no actions will be executed\n"
530
683
        # to the jails template directory (it will be used as a template
531
684
        # for all the students' jails).
532
685
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
686
    if not nosubjects:
 
687
        # Copy the subjects directory across
 
688
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
689
            srcdir="./subjects")
 
690
 
 
691
    # Append IVLE path to ivle.pth in python site packages
 
692
    # (Unless it's already there)
 
693
    ivle_pth = os.path.join(sys.prefix,
 
694
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
695
    ivle_www = os.path.join(ivle_install_dir, "www")
 
696
    write_ivle_pth = True
 
697
    try:
 
698
        file = open(ivle_pth, 'r')
 
699
        for line in file:
 
700
            if line.strip() == ivle_www:
 
701
                write_ivle_pth = False
 
702
                break
 
703
    except (IOError, OSError):
 
704
        pass
 
705
    if write_ivle_pth:
 
706
        action_append(ivle_pth, ivle_www)
 
707
 
 
708
    return 0
 
709
 
 
710
def updatejails(args):
 
711
    # Get "dry" variable from command line
 
712
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
713
    opts = dict(opts)
 
714
    dry = '-n' in opts or '--dry' in opts
 
715
 
 
716
    if dry:
 
717
        print "Dry run (no actions will be executed\n"
 
718
 
 
719
    if not dry and os.geteuid() != 0:
 
720
        print >>sys.stderr, "Must be root to run install"
 
721
        print >>sys.stderr, "(I need to chown some files)."
 
722
        return 1
 
723
 
 
724
    # Update the template jail directory in case it hasn't been installed
 
725
    # recently.
 
726
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
727
 
 
728
    # Re-link all the files in all students jails.
 
729
    for dir in os.listdir(jail_base):
 
730
        if dir == 'template': continue
 
731
        # First back up the student's home directory
 
732
        temp_home = os.tmpnam()
 
733
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
734
        # Delete the student's jail and relink the jail files
 
735
        action_linktree(os.path.join(jail_base, 'template'),
 
736
            os.path.join(jail_base, dir), dry)
 
737
        # Restore the student's home directory
 
738
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
739
        # Set up the user's home directory just in case they don't have a
 
740
        # directory for this yet
 
741
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
533
742
 
534
743
    return 0
535
744
 
562
771
    if ret != 0:
563
772
        raise RunError(prog, ret)
564
773
 
 
774
def action_rename(src, dst, dry):
 
775
    """Calls rename. Deletes the target if it already exists."""
 
776
    if os.access(dst, os.F_OK):
 
777
        print "rm -r", dst
 
778
        if not dry:
 
779
            shutil.rmtree(dst, True)
 
780
    print "mv ", src, dst
 
781
    if dry: return
 
782
    try:
 
783
        os.rename(src, dst)
 
784
    except OSError, (err, msg):
 
785
        if err != errno.EEXIST:
 
786
            raise
 
787
 
565
788
def action_mkdir(path, dry):
566
789
    """Calls mkdir. Silently ignored if the directory already exists.
567
790
    Creates all parent directories as necessary."""
587
810
    if dry: return
588
811
    shutil.copytree(src, dst, True)
589
812
 
590
 
def action_copylist(srclist, dst, dry):
 
813
def action_linktree(src, dst, dry):
 
814
    """Hard-links an entire directory tree. Same as copytree but the created
 
815
    files are hard-links not actual copies. Removes the existing destination.
 
816
    """
 
817
    if os.access(dst, os.F_OK):
 
818
        print "rm -r", dst
 
819
        if not dry:
 
820
            shutil.rmtree(dst, True)
 
821
    print "<cp with hardlinks> -r", src, dst
 
822
    if dry: return
 
823
    common.makeuser.linktree(src, dst)
 
824
 
 
825
def action_copylist(srclist, dst, dry, srcdir="."):
591
826
    """Copies all files in a list to a new location. The files in the list
592
827
    are read relative to the current directory, and their destinations are the
593
828
    same paths relative to dst. Creates all parent directories as necessary.
 
829
    srcdir is "." by default, can be overridden.
594
830
    """
595
831
    for srcfile in srclist:
596
832
        dstfile = os.path.join(dst, srcfile)
 
833
        srcfile = os.path.join(srcdir, srcfile)
597
834
        dstdir = os.path.split(dstfile)[0]
598
835
        if not os.path.isdir(dstdir):
599
836
            action_mkdir(dstdir, dry)
608
845
def action_copyfile(src, dst, dry):
609
846
    """Copies one file to a new location. Creates all parent directories
610
847
    as necessary.
 
848
    Warn if file not found.
611
849
    """
612
850
    dstdir = os.path.split(dst)[0]
613
851
    if not os.path.isdir(dstdir):
617
855
        try:
618
856
            shutil.copyfile(src, dst)
619
857
            shutil.copymode(src, dst)
620
 
        except shutil.Error:
621
 
            pass
 
858
        except (shutil.Error, IOError), e:
 
859
            print "Warning: " + str(e)
622
860
 
623
861
def action_symlink(src, dst, dry):
624
862
    """Creates a symlink in a given location. Creates all parent directories
634
872
    if not dry:
635
873
        os.symlink(src, dst)
636
874
 
 
875
def action_append(ivle_pth, ivle_www):
 
876
    file = open(ivle_pth, 'a+')
 
877
    file.write(ivle_www + '\n')
 
878
    file.close()
 
879
 
637
880
def action_chown_setuid(file, dry):
638
881
    """Chowns a file to root, and sets the setuid bit on the file.
639
882
    Calling this function requires the euid to be root.
648
891
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
649
892
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
650
893
 
 
894
def action_chmod_x(file, dry):
 
895
    """Chmod +xs a file (sets execute permission)."""
 
896
    print "chmod u+rwx", file
 
897
    if not dry:
 
898
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
 
899
 
651
900
def query_user(default, prompt):
652
901
    """Prompts the user for a string, which is read from a line of stdin.
653
902
    Exits silently if EOF is encountered. Returns the string, with spaces