138
# Disallow run as root unless installing
139
if operation != 'install' and os.geteuid() == 0:
140
print >>sys.stderr, "I do not want to run this stage as root."
141
print >>sys.stderr, "Please run as a normal user."
115
143
# Call the requested operation's function
121
149
'listmake' : listmake,
122
150
'install' : install,
123
}[operation](argv[2:])
125
153
print >>sys.stderr, (
126
154
"""Invalid operation '%s'. Try python setup.py help."""
131
opts, args = getopt.getopt(argv[1:], "h", ["help"])
132
except getopt.error, msg:
134
# more code, unchanged
136
print >>sys.stderr, err.msg
137
print >>sys.stderr, "for help use --help"
157
return oper_func(argv[2:])
140
159
# Operation functions
158
178
if operation == 'help':
159
179
print """python setup.py help [operation]
160
180
Prints the usage message or detailed help on an operation, then exits."""
181
elif operation == 'listmake':
182
print """python setup.py listmake
183
(For developer use only)
184
Recurses through the source tree and builds a list of all files which should
185
be copied upon installation. This should be run by the developer before
186
cutting a distribution, and the listfile it generates should be included in
187
the distribution, avoiding the administrator having to run it."""
161
188
elif operation == 'conf':
162
189
print """python setup.py conf [args]
163
190
Configures IVLE with machine-specific details, most notably, various paths.
164
191
Either prompts the administrator for these details or accepts them as
192
command-line args. Will be interactive only if there are no arguments given.
193
Takes defaults from existing conf file if it exists.
195
To run IVLE out of the source directory (allowing development without having
196
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
197
directory, and run build/install one time.
166
199
Creates www/conf/conf.py and trampoline/conf.h.
206
As explained in the interactive prompt or conf.py.
169
208
elif operation == 'build':
170
print """python setup.py build
209
print """python -O setup.py build [--dry|-n]
171
210
Compiles all files and sets up a jail template in the source directory.
211
-O is recommended to cause compilation to be optimised.
173
213
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
176
216
Copies console/ to a location within the jail.
177
217
Copies OS programs and files to corresponding locations within the jail
178
218
(eg. python and Python libs, ld.so, etc).
179
Generates .pyc files for all the IVLE .py files."""
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."""
219
Generates .pyc or .pyo files for all the IVLE .py files.
221
--dry | -n Print out the actions but don't do anything."""
187
222
elif operation == 'install':
188
223
print """sudo python setup.py install [--nojail] [--dry|-n]
276
317
conffile = os.path.join(cwd, "www/conf/conf.py")
277
318
conf_hfile = os.path.join(cwd, "trampoline/conf.h")
279
# Fixed config options that we don't ask the admin
281
default_app = "dummy"
283
print """This tool will create the following files:
320
# Get command-line arguments to avoid asking questions.
322
(opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
323
'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
326
print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
330
# Interactive mode. Prompt the user for all the values.
332
print """This tool will create the following files:
286
335
prompting you for details about your configuration. The file will be
289
338
Please hit Ctrl+C now if you do not wish to do this.
290
339
""" % (conffile, conf_hfile)
292
# Get information from the administrator
293
# If EOF is encountered at any time during the questioning, just exit
341
# Get information from the administrator
342
# If EOF is encountered at any time during the questioning, just exit
296
root_dir = query_user(root_dir,
297
"""Root directory where IVLE is located (in URL space):""")
298
ivle_install_dir = query_user(ivle_install_dir,
299
'Root directory where IVLE will be installed (on the local file '
301
jail_base = query_user(jail_base,
302
"""Root directory where the jails (containing user files) are stored
345
root_dir = query_user(root_dir,
346
"""Root directory where IVLE is located (in URL space):""")
347
ivle_install_dir = query_user(ivle_install_dir,
348
'Root directory where IVLE will be installed (on the local file '
350
jail_base = query_user(jail_base,
351
"""Root directory where the jails (containing user files) are stored
303
352
(on the local file system):""")
304
allowed_uids = query_user(allowed_uids,
305
"""UID of the web server process which will run IVLE.
353
allowed_uids = query_user(allowed_uids,
354
"""UID of the web server process which will run IVLE.
306
355
Only this user may execute the trampoline. May specify multiple users as
307
356
a comma-separated list.
308
357
(eg. "1002,78")""")
361
# Non-interactive mode. Parse the options.
362
if '--root_dir' in opts:
363
root_dir = opts['--root_dir']
364
if '--ivle_install_dir' in opts:
365
ivle_install_dir = opts['--ivle_install_dir']
366
if '--jail_base' in opts:
367
jail_base = opts['--jail_base']
368
if '--allowed_uids' in opts:
369
allowed_uids = opts['--allowed_uids']
310
371
# Error handling on input values
313
373
allowed_uids = map(int, allowed_uids.split(','))
314
374
except ValueError:
398
dry = False # Set to True later if --dry
452
# Get "dry" variable from command line
453
(opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
455
dry = '-n' in opts or '--dry' in opts
458
print "Dry run (no actions will be executed\n"
400
460
# Compile the trampoline
401
461
action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
402
462
'trampoline/trampoline.c'], dry)
404
464
# Create the jail and its subdirectories
406
action_mkdir('jail/bin')
407
action_mkdir('jail/lib')
408
action_mkdir('jail/usr/bin')
409
action_mkdir('jail/usr/lib')
410
action_mkdir('jail/opt/ivle')
411
action_mkdir('jail/home')
412
action_mkdir('jail/tmp')
414
# TODO: Copy console into the jail
415
# TODO: Copy operating system files into the jail
416
# TODO: Compile .py files into .pyc files
465
# Note: Other subdirs will be made by copying files
466
action_mkdir('jail', dry)
467
action_mkdir('jail/home', dry)
468
action_mkdir('jail/tmp', dry)
470
# Copy all console and operating system files into the jail
471
action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
472
copy_os_files_jail(dry)
474
# Compile .py files into .pyc or .pyo files
475
compileall.compile_dir('www', quiet=True)
476
compileall.compile_dir('console', quiet=True)
480
def copy_os_files_jail(dry):
481
"""Copies necessary Operating System files from their usual locations
482
into the jail/ directory of the cwd."""
483
# 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)
494
def copy_file_to_jail(src, dry):
495
"""Copies a single file from an absolute location into the same location
496
within the jail. src must begin with a '/'. The jail will be located
497
in a 'jail' subdirectory of the current path."""
498
action_copyfile(src, 'jail' + src, dry)
420
500
def install(args):
501
# Get "dry" and "nojail" variables from command line
502
(opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
504
dry = '-n' in opts or '--dry' in opts
505
nojail = '--nojail' in opts
508
print "Dry run (no actions will be executed\n"
510
if not dry and os.geteuid() != 0:
511
print >>sys.stderr, "Must be root to run install"
512
print >>sys.stderr, "(I need to chown some files)."
515
# Create the target (install) directory
516
action_mkdir(ivle_install_dir, dry)
518
# Create bin and copy the compiled files there
519
action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
520
tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
521
action_copyfile('trampoline/trampoline', tramppath, dry)
522
# chown trampoline to root and set setuid bit
523
action_chown_setuid(tramppath, dry)
525
# Copy the www directory using the list
526
action_copylist(install_list.list_www, ivle_install_dir, dry)
529
# Copy the local jail directory built by the build action
530
# to the jails template directory (it will be used as a template
531
# for all the students' jails).
532
action_copytree('jail', os.path.join(jail_base, 'template'), dry)
424
536
# The actions call Python os functions but print actions and handle dryness.
461
573
if err != errno.EEXIST:
576
def action_copytree(src, dst, dry):
577
"""Copies an entire directory tree. Symlinks are seen as normal files and
578
copies of the entire file (not the link) are made. Creates all parent
579
directories as necessary.
581
See shutil.copytree."""
582
if os.access(dst, os.F_OK):
585
shutil.rmtree(dst, True)
586
print "cp -r", src, dst
588
shutil.copytree(src, dst, True)
590
def action_copylist(srclist, dst, dry):
591
"""Copies all files in a list to a new location. The files in the list
592
are read relative to the current directory, and their destinations are the
593
same paths relative to dst. Creates all parent directories as necessary.
595
for srcfile in srclist:
596
dstfile = os.path.join(dst, srcfile)
597
dstdir = os.path.split(dstfile)[0]
598
if not os.path.isdir(dstdir):
599
action_mkdir(dstdir, dry)
600
print "cp -f", srcfile, dstfile
603
shutil.copyfile(srcfile, dstfile)
604
shutil.copymode(srcfile, dstfile)
608
def action_copyfile(src, dst, dry):
609
"""Copies one file to a new location. Creates all parent directories
612
dstdir = os.path.split(dst)[0]
613
if not os.path.isdir(dstdir):
614
action_mkdir(dstdir, dry)
615
print "cp -f", src, dst
618
shutil.copyfile(src, dst)
619
shutil.copymode(src, dst)
623
def action_symlink(src, dst, dry):
624
"""Creates a symlink in a given location. Creates all parent directories
627
dstdir = os.path.split(dst)[0]
628
if not os.path.isdir(dstdir):
629
action_mkdir(dstdir, dry)
630
# Delete existing file
631
if os.path.exists(dst):
633
print "ln -fs", src, dst
637
def action_chown_setuid(file, dry):
638
"""Chowns a file to root, and sets the setuid bit on the file.
639
Calling this function requires the euid to be root.
640
The actual mode of path is set to: rws--s--s
642
print "chown root:root", file
645
print "chmod a+xs", file
646
print "chmod u+rw", file
648
os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
649
| stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
464
651
def query_user(default, prompt):
465
652
"""Prompts the user for a string, which is read from a line of stdin.
466
653
Exits silently if EOF is encountered. Returns the string, with spaces