56
oper_func = call_operator(operation)
57
return oper_func(argv[2:])
61
print """Usage: python setup.py operation [options]
67
For help and options for a specific operation use 'help [operation]'."""
70
oper_func = call_operator(operator)
71
oper_func(['operator','--help'])
73
def call_operator(operation):
129
74
# Call the requested operation's function
135
'listmake' : listmake,
78
'build' : setup.build.build,
79
'install' : setup.install.install,
139
82
print >>sys.stderr, (
140
83
"""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)
523
# The actions call Python os functions but print actions and handle dryness.
524
# May still throw os exceptions if errors occur.
527
"""Represents an error when running a program (nonzero return)."""
528
def __init__(self, prog, retcode):
530
self.retcode = retcode
532
return str(self.prog) + " returned " + repr(self.retcode)
534
def action_runprog(prog, args, dry):
535
"""Runs a unix program. Searches in $PATH. Synchronous (waits for the
536
program to return). Runs in the current environment. First prints the
537
action as a "bash" line.
539
Throws a RunError with a retcode of the return value of the program,
540
if the program did not return 0.
542
prog: String. Name of the program. (No path required, if in $PATH).
543
args: [String]. Arguments to the program.
544
dry: Bool. If True, prints but does not execute.
546
print prog, string.join(args, ' ')
548
ret = os.spawnvp(os.P_WAIT, prog, args)
550
raise RunError(prog, ret)
552
def action_mkdir(path, dry):
553
"""Calls mkdir. Silently ignored if the directory already exists.
554
Creates all parent directories as necessary."""
555
print "mkdir -p", path
559
except OSError, (err, msg):
560
if err != errno.EEXIST:
563
def action_copytree(src, dst, dry):
564
"""Copies an entire directory tree. Symlinks are seen as normal files and
565
copies of the entire file (not the link) are made. Creates all parent
566
directories as necessary.
568
See shutil.copytree."""
569
if os.access(dst, os.F_OK):
572
shutil.rmtree(dst, True)
573
print "cp -r", src, dst
575
shutil.copytree(src, dst)
577
def action_copylist(srclist, dst, dry):
578
"""Copies all files in a list to a new location. The files in the list
579
are read relative to the current directory, and their destinations are the
580
same paths relative to dst. Creates all parent directories as necessary.
582
for srcfile in srclist:
583
dstfile = os.path.join(dst, srcfile)
584
dstdir = os.path.split(dstfile)[0]
585
if not os.path.isdir(dstdir):
586
action_mkdir(dstdir, dry)
587
print "cp -f", srcfile, dstfile
589
shutil.copyfile(srcfile, dstfile)
591
def action_copyfile(src, dst, dry):
592
"""Copies one file to a new location. Creates all parent directories
595
dstdir = os.path.split(dst)[0]
596
if not os.path.isdir(dstdir):
597
action_mkdir(dstdir, dry)
598
print "cp -f", src, dst
600
shutil.copyfile(src, dst)
602
def action_symlink(src, dst, dry):
603
"""Creates a symlink in a given location. Creates all parent directories
606
dstdir = os.path.split(dst)[0]
607
if not os.path.isdir(dstdir):
608
action_mkdir(dstdir, dry)
609
print "ln -s", src, dst
613
def action_chown_setuid(file, dry):
614
"""Chowns a file to root, and sets the setuid bit on the file.
615
Calling this function requires the euid to be root.
616
The actual mode of path is set to: rws--s--s
618
print "chown root:root", file
621
print "chmod a+xs", file
622
print "chmod u+rw", file
624
os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
625
| stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
627
def query_user(default, prompt):
628
"""Prompts the user for a string, which is read from a line of stdin.
629
Exits silently if EOF is encountered. Returns the string, with spaces
630
removed from the beginning and end.
632
Returns default if a 0-length line (after spaces removed) was read.
634
sys.stdout.write('%s\n (default: "%s")\n>' % (prompt, default))
636
val = sys.stdin.readline()
637
except KeyboardInterrupt:
639
sys.stdout.write("\n")
641
sys.stdout.write("\n")
643
if val == '': sys.exit(1)
644
# If empty line, return default
646
if val == '': return default
649
def filter_mutate(function, list):
650
"""Like built-in filter, but mutates the given list instead of returning a
651
new one. Returns None."""
654
# Delete elements which do not match
655
if not function(list[i]):
659
88
if __name__ == "__main__":