57
57
# Copy www/ to $target.
58
58
# Copy jail/ to jails template directory (unless --nojail specified).
60
# TODO: List in help, and handle, args for the conf operation
72
# Try importing existing conf, but if we can't just set up defaults
73
# The reason for this is that these settings are used by other phases
74
# of setup besides conf, so we need to know them.
75
# Also this allows you to hit Return to accept the existing value.
77
confmodule = __import__("www/conf/conf")
78
root_dir = confmodule.root_dir
79
ivle_install_dir = confmodule.ivle_install_dir
80
jail_base = confmodule.jail_base
82
# Just set reasonable defaults
84
ivle_install_dir = "/opt/ivle"
85
jail_base = "/home/informatics/jails"
89
# Try importing install_list, but don't fail if we can't, because listmake can
90
# function without it.
96
# Mime types which will automatically be placed in the list by listmake.
97
# Note that listmake is not intended to be run by the final user (the system
98
# administrator who installs this), so the developers can customize the list
99
# as necessary, and include it in the distribution.
100
listmake_mimetypes = ['text/x-python', 'text/html',
101
'application/x-javascript', 'application/javascript',
102
'text/css', 'image/png']
104
64
# Main function skeleton from Guido van Rossum
105
65
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
228
# We build two separate lists, by walking www and console
229
list_www = build_list_py_files('www')
230
list_console = build_list_py_files('console')
231
# Make sure that the files generated by conf are in the list
232
# (since listmake is typically run before conf)
233
if "www/conf/conf.py" not in list_www:
234
list_www.append("www/conf/conf.py")
235
# Make sure that console/python-console is in the list
236
if "console/python-console" not in list_console:
237
list_console.append("console/python-console")
238
# Write these out to a file
240
# the files that will be created/overwritten
241
listfile = os.path.join(cwd, "install_list.py")
244
file = open(listfile, "w")
246
file.write("""# IVLE Configuration File
248
# Provides lists of all files to be installed by `setup.py install' from
249
# certain directories.
250
# Note that any files with the given filename plus 'c' or 'o' (that is,
251
# compiled .pyc or .pyo files) will be copied as well.
253
# List of all installable files in www directory.
255
writelist_pretty(file, list_www)
257
# List of all installable files in console directory.
259
writelist_pretty(file, list_console)
262
except IOError, (errno, strerror):
263
print "IO error(%s): %s" % (errno, strerror)
266
print "Successfully wrote install_list.py"
269
print ("You may modify the set of installable files before cutting the "
276
def build_list_py_files(dir):
277
"""Builds a list of all py files found in a directory and its
278
subdirectories. Returns this as a list of strings."""
280
for (dirpath, dirnames, filenames) in os.walk(dir):
281
# Exclude directories beginning with a '.' (such as '.svn')
282
filter_mutate(lambda x: x[0] != '.', dirnames)
283
# All *.py files are added to the list
284
pylist += [os.path.join(dirpath, item) for item in filenames
285
if mimetypes.guess_type(item)[0] in listmake_mimetypes]
288
def writelist_pretty(file, list):
289
"""Writes a list one element per line, to a file."""
295
file.write(' %s,\n' % repr(elem))
299
global root_dir, ivle_install_dir, jail_base, allowed_uids
300
185
# Set up some variables
302
187
cwd = os.getcwd()
321
206
# If EOF is encountered at any time during the questioning, just exit
324
root_dir = query_user(root_dir,
325
"""Root directory where IVLE is located (in URL space):""")
326
ivle_install_dir = query_user(ivle_install_dir,
327
'Root directory where IVLE will be installed (on the local file '
329
jail_base = query_user(jail_base,
330
"""Root directory where the jails (containing user files) are stored
331
(on the local file system):""")
332
allowed_uids = query_user(allowed_uids,
333
"""UID of the web server process which will run IVLE.
334
Only this user may execute the trampoline. May specify multiple users as
335
a comma-separated list.
338
# Error handling on input values
341
allowed_uids = map(int, allowed_uids.split(','))
343
print >>sys.stderr, (
344
"Invalid UID list (%s).\n"
345
"Must be a comma-separated list of integers." % allowed_uids)
209
root_dir = query_user(
210
"""Root directory where IVLE is located (in URL space):
211
(eg. "/" or "/ivle")""")
212
ivle_install_dir = query_user(
213
'Root directory where IVLE is located (on the local file system):\n'
214
'(eg. "/home/informatics/ivle")')
215
student_dir = query_user(
216
"""Root directory where user files are stored (on the local file system):
217
(eg. "/home/informatics/jails")""")
348
219
# Write www/conf/conf.py
426
dry = False # Set to True later if --dry
428
# Compile the trampoline
429
action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
430
'trampoline/trampoline.c'], dry)
432
# Create the jail and its subdirectories
433
action_mkdir('jail', dry)
434
action_mkdir('jail/bin', dry)
435
action_mkdir('jail/lib', dry)
436
action_mkdir('jail/usr/bin', dry)
437
action_mkdir('jail/usr/lib', dry)
438
action_mkdir('jail/opt/ivle', dry)
439
action_mkdir('jail/home', dry)
440
action_mkdir('jail/tmp', dry)
442
# Copy all console files into the jail
443
action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
445
# TODO: Copy operating system files into the jail
447
# Compile .py files into .pyc or .pyo files
448
compileall.compile_dir('www', quiet=True)
449
compileall.compile_dir('console', quiet=True)
453
298
def install(args):
454
# Create the target directory
455
nojail = False # Set to True later if --nojail
456
dry = False # Set to True later if --dry
458
if not dry and os.geteuid() != 0:
459
print >>sys.stderr, "Must be root to run install"
460
print >>sys.stderr, "(I need to chown some files)."
463
# Create the target (install) directory
464
action_mkdir(ivle_install_dir, dry)
466
# Create bin and copy the compiled files there
467
action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
468
tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
469
action_copyfile('trampoline/trampoline', tramppath, dry)
470
# chown trampoline to root and set setuid bit
471
action_chown_setuid(tramppath, dry)
473
# Copy the www directory using the list
474
action_copylist(install_list.list_www, ivle_install_dir, dry)
477
# Copy the local jail directory built by the build action
478
# to the jails template directory (it will be used as a template
479
# for all the students' jails).
480
action_copytree('jail', os.path.join(jail_base, 'template'), dry)
484
# The actions call Python os functions but print actions and handle dryness.
485
# May still throw os exceptions if errors occur.
488
"""Represents an error when running a program (nonzero return)."""
489
def __init__(self, prog, retcode):
491
self.retcode = retcode
493
return str(self.prog) + " returned " + repr(self.retcode)
495
def action_runprog(prog, args, dry):
496
"""Runs a unix program. Searches in $PATH. Synchronous (waits for the
497
program to return). Runs in the current environment. First prints the
498
action as a "bash" line.
500
Throws a RunError with a retcode of the return value of the program,
501
if the program did not return 0.
503
prog: String. Name of the program. (No path required, if in $PATH).
504
args: [String]. Arguments to the program.
505
dry: Bool. If True, prints but does not execute.
507
print prog, string.join(args, ' ')
509
ret = os.spawnvp(os.P_WAIT, prog, args)
511
raise RunError(prog, ret)
513
def action_mkdir(path, dry):
514
"""Calls mkdir. Silently ignored if the directory already exists.
515
Creates all parent directories as necessary."""
516
print "mkdir -p", path
520
except OSError, (err, msg):
521
if err != errno.EEXIST:
524
def action_copytree(src, dst, dry):
525
"""Copies an entire directory tree. Symlinks are seen as normal files and
526
copies of the entire file (not the link) are made. Creates all parent
527
directories as necessary.
529
See shutil.copytree."""
530
if os.access(dst, os.F_OK):
533
shutil.rmtree(dst, True)
534
print "cp -r", src, dst
536
shutil.copytree(src, dst)
538
def action_copylist(srclist, dst, dry):
539
"""Copies all files in a list to a new location. The files in the list
540
are read relative to the current directory, and their destinations are the
541
same paths relative to dst. Creates all parent directories as necessary.
543
for srcfile in srclist:
544
dstfile = os.path.join(dst, srcfile)
545
dstdir = os.path.split(dstfile)[0]
546
if not os.path.isdir(dstdir):
547
action_mkdir(dstdir, dry)
548
print "cp -f", srcfile, dstfile
550
shutil.copyfile(srcfile, dstfile)
552
def action_copyfile(src, dst, dry):
553
"""Copies one file to a new location. Creates all parent directories
556
dstdir = os.path.split(dst)[0]
557
if not os.path.isdir(dstdir):
558
action_mkdir(dstdir, dry)
559
print "cp -f", src, dst
561
shutil.copyfile(src, dst)
563
def action_chown_setuid(file, dry):
564
"""Chowns a file to root, and sets the setuid bit on the file.
565
Calling this function requires the euid to be root.
566
The actual mode of path is set to: rws--s--s
568
print "chown root:root", file
571
print "chmod a+xs", file
572
print "chmod u+rw", file
574
os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
575
| stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
577
def query_user(default, prompt):
302
def query_user(prompt):
578
303
"""Prompts the user for a string, which is read from a line of stdin.
579
304
Exits silently if EOF is encountered. Returns the string, with spaces
580
305
removed from the beginning and end.
582
Returns default if a 0-length line (after spaces removed) was read.
584
sys.stdout.write('%s\n (default: "%s")\n>' % (prompt, default))
307
sys.stdout.write(prompt)
308
sys.stdout.write("\n>")
586
310
val = sys.stdin.readline()
587
311
except KeyboardInterrupt: