58
oper_func = call_operator(operation)
59
return oper_func(argv[2:])
63
print """Usage: python setup.py operation [options]
70
For help and options for a specific operation use 'help [operation]'."""
73
oper_func = call_operator(operator)
74
oper_func(['operator','--help'])
76
def call_operator(operation):
129
77
# Call the requested operation's function
135
'listmake' : listmake,
81
'config' : setup.configure.configure,
82
'build' : setup.build.build,
83
'install' : setup.install.install,
84
#'updatejails' : None,
139
87
print >>sys.stderr, (
140
88
"""Invalid operation '%s'. Try python setup.py help."""
142
return oper_func(argv[2:])
144
# Operation functions
148
print """Usage: python setup.py operation [args]
149
Operation (and args) can be:
153
install [--nojail] [-n|--dry]
157
print """Usage: python setup.py help [operation]"""
162
if operation == 'help':
163
print """python setup.py help [operation]
164
Prints the usage message or detailed help on an operation, then exits."""
165
elif operation == 'listmake':
166
print """python setup.py listmake
167
(For developer use only)
168
Recurses through the source tree and builds a list of all files which should
169
be copied upon installation. This should be run by the developer before
170
cutting a distribution, and the listfile it generates should be included in
171
the distribution, avoiding the administrator having to run it."""
172
elif operation == 'conf':
173
print """python setup.py conf [args]
174
Configures IVLE with machine-specific details, most notably, various paths.
175
Either prompts the administrator for these details or accepts them as
176
command-line args. Will be interactive only if there are no arguments given.
177
Takes defaults from existing conf file if it exists.
178
Creates www/conf/conf.py and trampoline/conf.h.
184
As explained in the interactive prompt or conf.py.
186
elif operation == 'build':
187
print """python -O setup.py build [--dry|-n]
188
Compiles all files and sets up a jail template in the source directory.
189
-O is recommended to cause compilation to be optimised.
191
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
193
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
194
Copies console/ to a location within the jail.
195
Copies OS programs and files to corresponding locations within the jail
196
(eg. python and Python libs, ld.so, etc).
197
Generates .pyc or .pyo files for all the IVLE .py files.
199
--dry | -n Print out the actions but don't do anything."""
200
elif operation == 'install':
201
print """sudo python setup.py install [--nojail] [--dry|-n]
203
Create target install directory ($target).
205
Copy trampoline/trampoline to $target/bin.
206
chown and chmod the installed trampoline.
207
Copy www/ to $target.
208
Copy jail/ to jails template directory (unless --nojail specified).
210
--nojail Do not copy the jail.
211
--dry | -n Print out the actions but don't do anything."""
213
print >>sys.stderr, (
214
"""Invalid operation '%s'. Try python setup.py help."""
219
# We build two separate lists, by walking www and console
220
list_www = build_list_py_files('www')
221
list_console = build_list_py_files('console')
222
# Make sure that the files generated by conf are in the list
223
# (since listmake is typically run before conf)
224
if "www/conf/conf.py" not in list_www:
225
list_www.append("www/conf/conf.py")
226
# Make sure that console/python-console is in the list
227
if "console/python-console" not in list_console:
228
list_console.append("console/python-console")
229
# Write these out to a file
231
# the files that will be created/overwritten
232
listfile = os.path.join(cwd, "install_list.py")
235
file = open(listfile, "w")
237
file.write("""# IVLE Configuration File
239
# Provides lists of all files to be installed by `setup.py install' from
240
# certain directories.
241
# Note that any files with the given filename plus 'c' or 'o' (that is,
242
# compiled .pyc or .pyo files) will be copied as well.
244
# List of all installable files in www directory.
246
writelist_pretty(file, list_www)
248
# List of all installable files in console directory.
250
writelist_pretty(file, list_console)
253
except IOError, (errno, strerror):
254
print "IO error(%s): %s" % (errno, strerror)
257
print "Successfully wrote install_list.py"
260
print ("You may modify the set of installable files before cutting the "
267
def build_list_py_files(dir):
268
"""Builds a list of all py files found in a directory and its
269
subdirectories. Returns this as a list of strings."""
271
for (dirpath, dirnames, filenames) in os.walk(dir):
272
# Exclude directories beginning with a '.' (such as '.svn')
273
filter_mutate(lambda x: x[0] != '.', dirnames)
274
# All *.py files are added to the list
275
pylist += [os.path.join(dirpath, item) for item in filenames
276
if mimetypes.guess_type(item)[0] in listmake_mimetypes]
279
def writelist_pretty(file, list):
280
"""Writes a list one element per line, to a file."""
286
file.write(' %s,\n' % repr(elem))
290
global root_dir, ivle_install_dir, jail_base, allowed_uids
291
# Set up some variables
294
# the files that will be created/overwritten
295
conffile = os.path.join(cwd, "www/conf/conf.py")
296
conf_hfile = os.path.join(cwd, "trampoline/conf.h")
298
# Fixed config options that we don't ask the admin
299
default_app = "dummy"
301
# Get command-line arguments to avoid asking questions.
303
(opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
304
'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
307
print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
311
# Interactive mode. Prompt the user for all the values.
313
print """This tool will create the following files:
316
prompting you for details about your configuration. The file will be
317
overwritten if it already exists. It will *not* install or deploy IVLE.
319
Please hit Ctrl+C now if you do not wish to do this.
320
""" % (conffile, conf_hfile)
322
# Get information from the administrator
323
# If EOF is encountered at any time during the questioning, just exit
326
root_dir = query_user(root_dir,
327
"""Root directory where IVLE is located (in URL space):""")
328
ivle_install_dir = query_user(ivle_install_dir,
329
'Root directory where IVLE will be installed (on the local file '
331
jail_base = query_user(jail_base,
332
"""Root directory where the jails (containing user files) are stored
333
(on the local file system):""")
334
allowed_uids = query_user(allowed_uids,
335
"""UID of the web server process which will run IVLE.
336
Only this user may execute the trampoline. May specify multiple users as
337
a comma-separated list.
342
# Non-interactive mode. Parse the options.
343
if '--root_dir' in opts:
344
root_dir = opts['--root_dir']
345
if '--ivle_install_dir' in opts:
346
ivle_install_dir = opts['--ivle_install_dir']
347
if '--jail_base' in opts:
348
jail_base = opts['--jail_base']
349
if '--allowed_uids' in opts:
350
allowed_uids = opts['--allowed_uids']
352
# Error handling on input values
354
allowed_uids = map(int, allowed_uids.split(','))
356
print >>sys.stderr, (
357
"Invalid UID list (%s).\n"
358
"Must be a comma-separated list of integers." % allowed_uids)
361
# Write www/conf/conf.py
364
conf = open(conffile, "w")
366
conf.write("""# IVLE Configuration File
368
# Miscellaneous application settings
371
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
373
# eg. "/" or "/ivle".
376
# In the local file system, where IVLE is actually installed.
377
# This directory should contain the "www" and "bin" directories.
378
ivle_install_dir = "%s"
380
# In the local file system, where are the student/user file spaces located.
381
# The user jails are expected to be located immediately in subdirectories of
385
# Which application to load by default (if the user navigates to the top level
386
# of the site). This is the app's URL name.
387
# Note that if this app requires authentication, the user will first be
388
# presented with the login screen.
390
""" % (root_dir, ivle_install_dir, jail_base, default_app))
393
except IOError, (errno, strerror):
394
print "IO error(%s): %s" % (errno, strerror)
397
print "Successfully wrote www/conf/conf.py"
399
# Write trampoline/conf.h
402
conf = open(conf_hfile, "w")
404
conf.write("""/* IVLE Configuration File
406
* Administrator settings required by trampoline.
407
* Note: trampoline will have to be rebuilt in order for changes to this file
411
/* In the local file system, where are the jails located.
412
* The trampoline does not allow the creation of a jail anywhere besides
413
* jail_base or a subdirectory of jail_base.
415
static const char* jail_base = "%s";
417
/* Which user IDs are allowed to run the trampoline.
418
* This list should be limited to the web server user.
419
* (Note that root is an implicit member of this list).
421
static const int allowed_uids[] = { %s };
422
""" % (jail_base, repr(allowed_uids)[1:-1]))
425
except IOError, (errno, strerror):
426
print "IO error(%s): %s" % (errno, strerror)
429
print "Successfully wrote trampoline/conf.h"
432
print "You may modify the configuration at any time by editing"
439
# Get "dry" variable from command line
440
(opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
442
dry = '-n' in opts or '--dry' in opts
445
print "Dry run (no actions will be executed\n"
447
# Compile the trampoline
448
action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
449
'trampoline/trampoline.c'], dry)
451
# Create the jail and its subdirectories
452
# Note: Other subdirs will be made by copying files
453
action_mkdir('jail', dry)
454
action_mkdir('jail/home', dry)
455
action_mkdir('jail/tmp', dry)
457
# Copy all console and operating system files into the jail
458
action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
459
copy_os_files_jail(dry)
461
# Compile .py files into .pyc or .pyo files
462
compileall.compile_dir('www', quiet=True)
463
compileall.compile_dir('console', quiet=True)
467
def copy_os_files_jail(dry):
468
"""Copies necessary Operating System files from their usual locations
469
into the jail/ directory of the cwd."""
470
# Currently source paths are configured for Ubuntu.
471
copy_file_to_jail('/lib/ld-linux.so.2', dry)
472
copy_file_to_jail('/lib/tls/i686/cmov/libc.so.6', dry)
473
copy_file_to_jail('/lib/tls/i686/cmov/libdl.so.2', dry)
474
copy_file_to_jail('/lib/tls/i686/cmov/libm.so.6', dry)
475
copy_file_to_jail('/lib/tls/i686/cmov/libpthread.so.0', dry)
476
copy_file_to_jail('/lib/tls/i686/cmov/libutil.so.1', dry)
477
copy_file_to_jail('/usr/bin/python2.5', dry)
478
# TODO: ln -s jail/usr/bin/python2.5 jail/usr/bin/python
479
action_copytree('/usr/lib/python2.5', 'jail/usr/lib/python2.5', dry)
481
def copy_file_to_jail(src, dry):
482
"""Copies a single file from an absolute location into the same location
483
within the jail. src must begin with a '/'. The jail will be located
484
in a 'jail' subdirectory of the current path."""
485
action_copyfile(src, 'jail' + src, dry)
488
# Get "dry" and "nojail" variables from command line
489
(opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
491
dry = '-n' in opts or '--dry' in opts
492
nojail = '--nojail' in opts
495
print "Dry run (no actions will be executed\n"
497
if not dry and os.geteuid() != 0:
498
print >>sys.stderr, "Must be root to run install"
499
print >>sys.stderr, "(I need to chown some files)."
502
# Create the target (install) directory
503
action_mkdir(ivle_install_dir, dry)
505
# Create bin and copy the compiled files there
506
action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
507
tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
508
action_copyfile('trampoline/trampoline', tramppath, dry)
509
# chown trampoline to root and set setuid bit
510
action_chown_setuid(tramppath, dry)
512
# Copy the www directory using the list
513
action_copylist(install_list.list_www, ivle_install_dir, dry)
516
# Copy the local jail directory built by the build action
517
# to the jails template directory (it will be used as a template
518
# for all the students' jails).
519
action_copytree('jail', os.path.join(jail_base, 'template'), dry)
520
# Set up symlinks inside the jail
521
action_symlink(os.path.join(jail_base, 'template/usr/bin/python2.5'),
522
os.path.join(jail_base, 'template/usr/bin/python'), dry)
526
# The actions call Python os functions but print actions and handle dryness.
527
# May still throw os exceptions if errors occur.
530
"""Represents an error when running a program (nonzero return)."""
531
def __init__(self, prog, retcode):
533
self.retcode = retcode
535
return str(self.prog) + " returned " + repr(self.retcode)
537
def action_runprog(prog, args, dry):
538
"""Runs a unix program. Searches in $PATH. Synchronous (waits for the
539
program to return). Runs in the current environment. First prints the
540
action as a "bash" line.
542
Throws a RunError with a retcode of the return value of the program,
543
if the program did not return 0.
545
prog: String. Name of the program. (No path required, if in $PATH).
546
args: [String]. Arguments to the program.
547
dry: Bool. If True, prints but does not execute.
549
print prog, string.join(args, ' ')
551
ret = os.spawnvp(os.P_WAIT, prog, args)
553
raise RunError(prog, ret)
555
def action_mkdir(path, dry):
556
"""Calls mkdir. Silently ignored if the directory already exists.
557
Creates all parent directories as necessary."""
558
print "mkdir -p", path
562
except OSError, (err, msg):
563
if err != errno.EEXIST:
566
def action_copytree(src, dst, dry):
567
"""Copies an entire directory tree. Symlinks are seen as normal files and
568
copies of the entire file (not the link) are made. Creates all parent
569
directories as necessary.
571
See shutil.copytree."""
572
if os.access(dst, os.F_OK):
575
shutil.rmtree(dst, True)
576
print "cp -r", src, dst
578
shutil.copytree(src, dst)
580
def action_copylist(srclist, dst, dry):
581
"""Copies all files in a list to a new location. The files in the list
582
are read relative to the current directory, and their destinations are the
583
same paths relative to dst. Creates all parent directories as necessary.
585
for srcfile in srclist:
586
dstfile = os.path.join(dst, srcfile)
587
dstdir = os.path.split(dstfile)[0]
588
if not os.path.isdir(dstdir):
589
action_mkdir(dstdir, dry)
590
print "cp -f", srcfile, dstfile
592
shutil.copyfile(srcfile, dstfile)
594
def action_copyfile(src, dst, dry):
595
"""Copies one file to a new location. Creates all parent directories
598
dstdir = os.path.split(dst)[0]
599
if not os.path.isdir(dstdir):
600
action_mkdir(dstdir, dry)
601
print "cp -f", src, dst
603
shutil.copyfile(src, dst)
605
def action_symlink(src, dst, dry):
606
"""Creates a symlink in a given location. Creates all parent directories
609
dstdir = os.path.split(dst)[0]
610
if not os.path.isdir(dstdir):
611
action_mkdir(dstdir, dry)
612
print "ln -s", src, dst
616
def action_chown_setuid(file, dry):
617
"""Chowns a file to root, and sets the setuid bit on the file.
618
Calling this function requires the euid to be root.
619
The actual mode of path is set to: rws--s--s
621
print "chown root:root", file
624
print "chmod a+xs", file
625
print "chmod u+rw", file
627
os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
628
| stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
630
def query_user(default, prompt):
631
"""Prompts the user for a string, which is read from a line of stdin.
632
Exits silently if EOF is encountered. Returns the string, with spaces
633
removed from the beginning and end.
635
Returns default if a 0-length line (after spaces removed) was read.
637
sys.stdout.write('%s\n (default: "%s")\n>' % (prompt, default))
639
val = sys.stdin.readline()
640
except KeyboardInterrupt:
642
sys.stdout.write("\n")
644
sys.stdout.write("\n")
646
if val == '': sys.exit(1)
647
# If empty line, return default
649
if val == '': return default
652
def filter_mutate(function, list):
653
"""Like built-in filter, but mutates the given list instead of returning a
654
new one. Returns None."""
657
# Delete elements which do not match
658
if not function(list[i]):
662
93
if __name__ == "__main__":