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):
134
77
# Call the requested operation's function
140
'listmake' : listmake,
142
}[operation](argv[2:])
81
'config' : setup.configure.configure,
82
'build' : setup.build.build,
83
'install' : setup.install.install,
84
#'updatejails' : None,
144
87
print >>sys.stderr, (
145
88
"""Invalid operation '%s'. Try python setup.py help."""
150
opts, args = getopt.getopt(argv[1:], "h", ["help"])
151
except getopt.error, msg:
153
# more code, unchanged
155
print >>sys.stderr, err.msg
156
print >>sys.stderr, "for help use --help"
159
# Operation functions
163
print """Usage: python setup.py operation [args]
164
Operation (and args) can be:
168
install [--nojail] [-n|--dry]
172
print """Usage: python setup.py help [operation]"""
177
if operation == 'help':
178
print """python setup.py help [operation]
179
Prints the usage message or detailed help on an operation, then exits."""
180
elif operation == 'listmake':
181
print """python setup.py listmake
182
(For developer use only)
183
Recurses through the source tree and builds a list of all files which should
184
be copied upon installation. This should be run by the developer before
185
cutting a distribution, and the listfile it generates should be included in
186
the distribution, avoiding the administrator having to run it."""
187
elif operation == 'conf':
188
print """python setup.py conf [args]
189
Configures IVLE with machine-specific details, most notably, various paths.
190
Either prompts the administrator for these details or accepts them as
192
Creates www/conf/conf.py and trampoline/conf.h.
195
elif operation == 'build':
196
print """python -O setup.py build [--dry|-n]
197
Compiles all files and sets up a jail template in the source directory.
198
-O is recommended to cause compilation to be optimised.
200
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
202
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
203
Copies console/ to a location within the jail.
204
Copies OS programs and files to corresponding locations within the jail
205
(eg. python and Python libs, ld.so, etc).
206
Generates .pyc or .pyo files for all the IVLE .py files.
208
--dry | -n Print out the actions but don't do anything."""
209
elif operation == 'install':
210
print """sudo python setup.py install [--nojail] [--dry|-n]
212
Create target install directory ($target).
214
Copy trampoline/trampoline to $target/bin.
215
chown and chmod the installed trampoline.
216
Copy www/ to $target.
217
Copy jail/ to jails template directory (unless --nojail specified).
219
--nojail Do not copy the jail.
220
--dry | -n Print out the actions but don't do anything."""
222
print >>sys.stderr, (
223
"""Invalid operation '%s'. Try python setup.py help."""
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
# Set up some variables
303
# the files that will be created/overwritten
304
conffile = os.path.join(cwd, "www/conf/conf.py")
305
conf_hfile = os.path.join(cwd, "trampoline/conf.h")
307
# Fixed config options that we don't ask the admin
309
default_app = "dummy"
311
print """This tool will create the following files:
314
prompting you for details about your configuration. The file will be
315
overwritten if it already exists. It will *not* install or deploy IVLE.
317
Please hit Ctrl+C now if you do not wish to do this.
318
""" % (conffile, conf_hfile)
320
# Get information from the administrator
321
# 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)
348
# Write www/conf/conf.py
351
conf = open(conffile, "w")
353
conf.write("""# IVLE Configuration File
355
# Miscellaneous application settings
358
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
360
# eg. "/" or "/ivle".
363
# In the local file system, where IVLE is actually installed.
364
# This directory should contain the "www" and "bin" directories.
365
ivle_install_dir = "%s"
367
# In the local file system, where are the student/user file spaces located.
368
# The user jails are expected to be located immediately in subdirectories of
372
# Which application to load by default (if the user navigates to the top level
373
# of the site). This is the app's URL name.
374
# Note that if this app requires authentication, the user will first be
375
# presented with the login screen.
377
""" % (root_dir, ivle_install_dir, jail_base, default_app))
380
except IOError, (errno, strerror):
381
print "IO error(%s): %s" % (errno, strerror)
384
print "Successfully wrote www/conf/conf.py"
386
# Write trampoline/conf.h
389
conf = open(conf_hfile, "w")
391
conf.write("""/* IVLE Configuration File
393
* Administrator settings required by trampoline.
394
* Note: trampoline will have to be rebuilt in order for changes to this file
398
/* In the local file system, where are the jails located.
399
* The trampoline does not allow the creation of a jail anywhere besides
400
* jail_base or a subdirectory of jail_base.
402
static const char* jail_base = "%s";
404
/* Which user IDs are allowed to run the trampoline.
405
* This list should be limited to the web server user.
406
* (Note that root is an implicit member of this list).
408
static const int allowed_uids[] = { %s };
409
""" % (jail_base, repr(allowed_uids)[1:-1]))
412
except IOError, (errno, strerror):
413
print "IO error(%s): %s" % (errno, strerror)
416
print "Successfully wrote trampoline/conf.h"
419
print "You may modify the configuration at any time by editing"
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)
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):
578
"""Prompts the user for a string, which is read from a line of stdin.
579
Exits silently if EOF is encountered. Returns the string, with spaces
580
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))
586
val = sys.stdin.readline()
587
except KeyboardInterrupt:
589
sys.stdout.write("\n")
591
sys.stdout.write("\n")
593
if val == '': sys.exit(1)
594
# If empty line, return default
596
if val == '': return default
599
def filter_mutate(function, list):
600
"""Like built-in filter, but mutates the given list instead of returning a
601
new one. Returns None."""
604
# Delete elements which do not match
605
if not function(list[i]):
609
93
if __name__ == "__main__":