~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-02-19 00:54:28 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:500
db: get_user and get_users now return User objects instead of dictionaries.
    This is the major part of replacing dicts with User objects, as it
    propagates upwards.

Propagated User objects up through the following modules:
    listusers.py, dispatch.login, authenticate, userservice, forumutil
All of these now treat users as an object rather than a dict.

To save on the size of the changes so far, login still individually copies
fields over to the session (so the session does not yet store a user object;
that is the second part of this refactor).

WOO!! Revision 500 :)

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
# cutting a distribution, and the listfile it generates should be included in
32
32
# the distribution, avoiding the administrator having to run it.
33
33
 
34
 
# setup.py conf [args]
 
34
# setup.py config [args]
35
35
# Configures IVLE with machine-specific details, most notably, various paths.
36
36
# Either prompts the administrator for these details or accepts them as
37
37
# command-line args.
38
 
# Creates www/conf/conf.py and trampoline/conf.h.
 
38
# Creates lib/conf/conf.py and trampoline/conf.h.
39
39
 
40
40
# setup.py build
41
41
# Compiles all files and sets up a jail template in the source directory.
68
68
import compileall
69
69
import getopt
70
70
 
 
71
# Import modules from the website is tricky since they're in the www
 
72
# directory.
 
73
sys.path.append(os.path.join(os.getcwd(), 'lib'))
 
74
import conf
 
75
import common.makeuser
 
76
 
 
77
# Determine which Python version (2.4 or 2.5, for example) we are running,
 
78
# and use that as the filename to the Python directory.
 
79
# Just get the first 3 characters of sys.version.
 
80
PYTHON_VERSION = sys.version[0:3]
 
81
 
 
82
# Operating system files to copy over into the jail.
 
83
# These will be copied from the given place on the OS file system into the
 
84
# same place within the jail.
 
85
JAIL_FILES = [
 
86
    '/lib/ld-linux.so.2',
 
87
    '/lib/tls/i686/cmov/libc.so.6',
 
88
    '/lib/tls/i686/cmov/libdl.so.2',
 
89
    '/lib/tls/i686/cmov/libm.so.6',
 
90
    '/lib/tls/i686/cmov/libpthread.so.0',
 
91
    '/lib/tls/i686/cmov/libutil.so.1',
 
92
    '/etc/ld.so.conf',
 
93
    '/etc/ld.so.cache',
 
94
    # These 2 files do not exist in Ubuntu
 
95
    #'/etc/ld.so.preload',
 
96
    #'/etc/ld.so.nohwcap',
 
97
    # UNIX commands
 
98
    '/usr/bin/strace',
 
99
    '/bin/ls',
 
100
    '/bin/echo',
 
101
    # Needed by python
 
102
    '/usr/bin/python%s' % PYTHON_VERSION,
 
103
    # Needed by fileservice
 
104
    '/lib/libcom_err.so.2',
 
105
    '/lib/libcrypt.so.1',
 
106
    '/lib/libkeyutils.so.1',
 
107
    '/lib/libresolv.so.2',
 
108
    '/lib/librt.so.1',
 
109
    '/lib/libuuid.so.1',
 
110
    '/usr/lib/libapr-1.so.0',
 
111
    '/usr/lib/libaprutil-1.so.0',
 
112
    '/usr/lib/libdb-4.4.so',
 
113
    '/usr/lib/libexpat.so.1',
 
114
    '/usr/lib/libgcrypt.so.11',
 
115
    '/usr/lib/libgnutls.so.13',
 
116
    '/usr/lib/libgpg-error.so.0',
 
117
    '/usr/lib/libgssapi_krb5.so.2',
 
118
    '/usr/lib/libk5crypto.so.3',
 
119
    '/usr/lib/libkrb5.so.3',
 
120
    '/usr/lib/libkrb5support.so.0',
 
121
    '/usr/lib/liblber.so.2',
 
122
    '/usr/lib/libldap_r.so.2',
 
123
    '/usr/lib/libneon.so.26',
 
124
    '/usr/lib/libpq.so.5',
 
125
    '/usr/lib/libsasl2.so.2',
 
126
    '/usr/lib/libsqlite3.so.0',
 
127
    '/usr/lib/libsvn_client-1.so.1',
 
128
    '/usr/lib/libsvn_delta-1.so.1',
 
129
    '/usr/lib/libsvn_diff-1.so.1',
 
130
    '/usr/lib/libsvn_fs-1.so.1',
 
131
    '/usr/lib/libsvn_fs_base-1.so.1',
 
132
    '/usr/lib/libsvn_fs_fs-1.so.1',
 
133
    '/usr/lib/libsvn_ra-1.so.1',
 
134
    '/usr/lib/libsvn_ra_dav-1.so.1',
 
135
    '/usr/lib/libsvn_ra_local-1.so.1',
 
136
    '/usr/lib/libsvn_ra_svn-1.so.1',
 
137
    '/usr/lib/libsvn_repos-1.so.1',
 
138
    '/usr/lib/libsvn_subr-1.so.1',
 
139
    '/usr/lib/libsvn_wc-1.so.1',
 
140
    '/usr/lib/libtasn1.so.3',
 
141
    '/usr/lib/libxml2.so.2',
 
142
    # Needed by matplotlib
 
143
    '/usr/lib/i686/cmov/libssl.so.0.9.8',
 
144
    '/usr/lib/i686/cmov/libcrypto.so.0.9.8',
 
145
    '/lib/tls/i686/cmov/libnsl.so.1',
 
146
    '/usr/lib/libz.so.1',
 
147
    '/usr/lib/atlas/liblapack.so.3',
 
148
    '/usr/lib/atlas/libblas.so.3',
 
149
    '/usr/lib/libg2c.so.0',
 
150
    '/usr/lib/libstdc++.so.6',
 
151
    '/usr/lib/libfreetype.so.6',
 
152
    '/usr/lib/libpng12.so.0',
 
153
    '/usr/lib/libBLT.2.4.so.8.4',
 
154
    '/usr/lib/libtk8.4.so.0',
 
155
    '/usr/lib/libtcl8.4.so.0',
 
156
    '/usr/lib/tcl8.4/init.tcl',
 
157
    '/usr/lib/libX11.so.6',
 
158
    '/usr/lib/libXau.so.6',
 
159
    '/usr/lib/libXdmcp.so.6',
 
160
    '/lib/libgcc_s.so.1',
 
161
    '/etc/matplotlibrc',
 
162
]
 
163
# Symlinks to make within the jail. Src mapped to dst.
 
164
JAIL_LINKS = {
 
165
    'python%s' % PYTHON_VERSION: 'jail/usr/bin/python',
 
166
}
 
167
# Trees to copy. Src mapped to dst (these will be passed to action_copytree).
 
168
JAIL_COPYTREES = {
 
169
    '/usr/lib/python%s' % PYTHON_VERSION:
 
170
        'jail/usr/lib/python%s' % PYTHON_VERSION,
 
171
    '/usr/share/matplotlib': 'jail/usr/share/matplotlib',
 
172
    '/etc/ld.so.conf.d': 'jail/etc/ld.so.conf.d',
 
173
}
 
174
 
 
175
class ConfigOption:
 
176
    """A configuration option; one of the things written to conf.py."""
 
177
    def __init__(self, option_name, default, prompt, comment):
 
178
        """Creates a configuration option.
 
179
        option_name: Name of the variable in conf.py. Also name of the
 
180
            command-line argument to setup.py conf.
 
181
        default: Default value for this variable.
 
182
        prompt: (Short) string presented during the interactive prompt in
 
183
            setup.py conf.
 
184
        comment: (Long) comment string stored in conf.py. Each line of this
 
185
            string should begin with a '#'.
 
186
        """
 
187
        self.option_name = option_name
 
188
        self.default = default
 
189
        self.prompt = prompt
 
190
        self.comment = comment
 
191
 
 
192
# Configuration options, defaults and descriptions
 
193
config_options = []
 
194
config_options.append(ConfigOption("root_dir", "/",
 
195
    """Root directory where IVLE is located (in URL space):""",
 
196
    """
 
197
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
 
198
# with this).
 
199
# eg. "/" or "/ivle"."""))
 
200
config_options.append(ConfigOption("ivle_install_dir", "/opt/ivle",
 
201
    'Root directory where IVLE will be installed (on the local file '
 
202
    'system):',
 
203
    """
 
204
# In the local file system, where IVLE is actually installed.
 
205
# This directory should contain the "www" and "bin" directories."""))
 
206
config_options.append(ConfigOption("jail_base", "/home/informatics/jails",
 
207
    """Root directory where the jails (containing user files) are stored
 
208
(on the local file system):""",
 
209
    """
 
210
# In the local file system, where are the student/user file spaces located.
 
211
# The user jails are expected to be located immediately in subdirectories of
 
212
# this location."""))
 
213
config_options.append(ConfigOption("subjects_base",
 
214
    "/home/informatics/subjects",
 
215
    """Root directory where the subject directories (containing worksheets
 
216
and other per-subject files) are stored (on the local file system):""",
 
217
    """
 
218
# In the local file system, where are the per-subject file spaces located.
 
219
# The individual subject directories are expected to be located immediately
 
220
# in subdirectories of this location."""))
 
221
config_options.append(ConfigOption("problems_base",
 
222
    "/home/informatics/problems",
 
223
    """Root directory where the problem directories (containing
 
224
subject-independent problem sheets) are stored (on the local file
 
225
system):""",
 
226
    """
 
227
# In the local file system, where are the subject-independent problem sheet
 
228
# file spaces located."""))
 
229
config_options.append(ConfigOption("public_host", "public.localhost",
 
230
    """Hostname which will cause the server to go into "public mode",
 
231
providing login-free access to student's published work:""",
 
232
    """
 
233
# The server goes into "public mode" if the browser sends a request with this
 
234
# host. This is for security reasons - we only serve public student files on a
 
235
# separate domain to the main IVLE site.
 
236
# Public mode does not use cookies, and serves only public content.
 
237
# Private mode (normal mode) requires login, and only serves files relevant to
 
238
# the logged-in user."""))
 
239
config_options.append(ConfigOption("allowed_uids", "33",
 
240
    """UID of the web server process which will run IVLE.
 
241
Only this user may execute the trampoline. May specify multiple users as
 
242
a comma-separated list.
 
243
    (eg. "1002,78")""",
 
244
    """
 
245
# The User-ID of the web server process which will run IVLE, and any other
 
246
# users who are allowed to run the trampoline. This is stores as a string of
 
247
# comma-separated integers, simply because it is not used within Python, only
 
248
# used by the setup program to write to conf.h (see setup.py config)."""))
 
249
config_options.append(ConfigOption("db_host", "localhost",
 
250
    """PostgreSQL Database config
 
251
==========================
 
252
Hostname of the DB server:""",
 
253
    """
 
254
### PostgreSQL Database config ###
 
255
# Database server hostname"""))
 
256
config_options.append(ConfigOption("db_port", "5432",
 
257
    """Port of the DB server:""",
 
258
    """
 
259
# Database server port"""))
 
260
config_options.append(ConfigOption("db_dbname", "ivle",
 
261
    """Database name:""",
 
262
    """
 
263
# Database name"""))
 
264
config_options.append(ConfigOption("db_user", "postgres",
 
265
    """Username for DB server login:""",
 
266
    """
 
267
# Database username"""))
 
268
config_options.append(ConfigOption("db_password", "",
 
269
    """Password for DB server login:
 
270
    (Caution: This password is stored in plaintext in lib/conf/conf.py)""",
 
271
    """
 
272
# Database password"""))
 
273
config_options.append(ConfigOption("svn_conf", "/opt/ivle/svn/svn.conf",
 
274
    """The location of the subversion configuration file used by apache
 
275
to host the user repositories:""",
 
276
    """
 
277
# The location of the subversion configuration file used by
 
278
# apache to host the user repositories."""))
 
279
config_options.append(ConfigOption("svn_repo_path", "/home/informatics/repositories",
 
280
    """The root directory for the subversion repositories:""",
 
281
    """
 
282
# The root directory for the subversion repositories."""))
 
283
config_options.append(ConfigOption("svn_auth_ivle", "/opt/ivle/svn/ivle.auth",
 
284
    """The location of the password file used to authenticate users
 
285
of the subversion repository from the ivle server:""",
 
286
    """
 
287
# The location of the password file used to authenticate users
 
288
# of the subversion repository from the ivle server."""))
 
289
config_options.append(ConfigOption("svn_auth_local", "/opt/ivle/svn/local.auth",
 
290
    """The location of the password file used to authenticate local users
 
291
of the subversion repository:""",
 
292
    """
 
293
# The location of the password file used to authenticate local users
 
294
# of the subversion repository."""))
 
295
config_options.append(ConfigOption("usrmgt_host", "localhost",
 
296
    """The hostname where the usrmgt-server runs:""",
 
297
    """
 
298
# The hostname where the usrmgt-server runs."""))
 
299
config_options.append(ConfigOption("usrmgt_port", "2178",
 
300
    """The port where the usrmgt-server runs:""",
 
301
    """
 
302
# The port where the usrmgt-server runs."""))
 
303
config_options.append(ConfigOption("usrmgt_magic", "",
 
304
    """The password for the usrmgt-server:""",
 
305
    """
 
306
# The password for the usrmgt-server."""))
 
307
 
71
308
# Try importing existing conf, but if we can't just set up defaults
72
309
# The reason for this is that these settings are used by other phases
73
310
# of setup besides conf, so we need to know them.
74
311
# Also this allows you to hit Return to accept the existing value.
75
312
try:
76
 
    confmodule = __import__("www/conf/conf")
77
 
    try:
78
 
        root_dir = confmodule.root_dir
79
 
    except:
80
 
        root_dir = "/ivle"
81
 
    try:
82
 
        ivle_install_dir = confmodule.ivle_install_dir
83
 
    except:
84
 
        ivle_install_dir = "/opt/ivle"
85
 
    try:
86
 
        jail_base = confmodule.jail_base
87
 
    except:
88
 
        jail_base = "/home/informatics/jails"
 
313
    confmodule = __import__("lib/conf/conf")
 
314
    for opt in config_options:
 
315
        try:
 
316
            globals()[opt.option_name] = confmodule.__dict__[opt.option_name]
 
317
        except:
 
318
            globals()[opt.option_name] = opt.default
89
319
except ImportError:
90
320
    # Just set reasonable defaults
91
 
    root_dir = "/ivle"
92
 
    ivle_install_dir = "/opt/ivle"
93
 
    jail_base = "/home/informatics/jails"
94
 
# Always defaults
95
 
allowed_uids = "0"
 
321
    for opt in config_options:
 
322
        globals()[opt.option_name] = opt.default
96
323
 
97
324
# Try importing install_list, but don't fail if we can't, because listmake can
98
325
# function without it.
107
334
# as necessary, and include it in the distribution.
108
335
listmake_mimetypes = ['text/x-python', 'text/html',
109
336
    'application/x-javascript', 'application/javascript',
110
 
    'text/css', 'image/png']
 
337
    'text/css', 'image/png', 'application/xml']
111
338
 
112
339
# Main function skeleton from Guido van Rossum
113
340
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
136
363
        return 1
137
364
 
138
365
    # Disallow run as root unless installing
139
 
    if operation != 'install' and os.geteuid() == 0:
 
366
    if (operation != 'install' and operation != 'updatejails'
 
367
        and os.geteuid() == 0):
140
368
        print >>sys.stderr, "I do not want to run this stage as root."
141
369
        print >>sys.stderr, "Please run as a normal user."
142
370
        return 1
144
372
    try:
145
373
        oper_func = {
146
374
            'help' : help,
147
 
            'conf' : conf,
 
375
            'config' : conf,
148
376
            'build' : build,
149
377
            'listmake' : listmake,
150
378
            'install' : install,
 
379
            'updatejails' : updatejails,
151
380
        }[operation]
152
381
    except KeyError:
153
382
        print >>sys.stderr, (
164
393
Operation (and args) can be:
165
394
    help [operation]
166
395
    listmake (developer use only)
167
 
    conf [args]
 
396
    config [args]
168
397
    build
169
 
    install [--nojail] [-n|--dry]
 
398
    install [--nojail] [--nosubjects] [-n|--dry]
170
399
"""
171
400
        return 1
172
401
    elif len(args) != 1:
185
414
be copied upon installation. This should be run by the developer before
186
415
cutting a distribution, and the listfile it generates should be included in
187
416
the distribution, avoiding the administrator having to run it."""
188
 
    elif operation == 'conf':
189
 
        print """python setup.py conf [args]
 
417
    elif operation == 'config':
 
418
        print """python setup.py config [args]
190
419
Configures IVLE with machine-specific details, most notably, various paths.
191
420
Either prompts the administrator for these details or accepts them as
192
421
command-line args. Will be interactive only if there are no arguments given.
196
425
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
197
426
directory, and run build/install one time.
198
427
 
199
 
Creates www/conf/conf.py and trampoline/conf.h.
 
428
Creates lib/conf/conf.py and trampoline/conf.h.
200
429
 
201
 
Args are:
202
 
    --root_dir
203
 
    --ivle_install_dir
204
 
    --jail_base
205
 
    --allowed_uids
206
 
As explained in the interactive prompt or conf.py.
 
430
Args are:"""
 
431
        for opt in config_options:
 
432
            print "    --" + opt.option_name
 
433
        print """As explained in the interactive prompt or conf.py.
207
434
"""
208
435
    elif operation == 'build':
209
436
        print """python -O setup.py build [--dry|-n]
220
447
 
221
448
--dry | -n  Print out the actions but don't do anything."""
222
449
    elif operation == 'install':
223
 
        print """sudo python setup.py install [--nojail] [--dry|-n]
 
450
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
224
451
(Requires root)
225
452
Create target install directory ($target).
226
453
Create $target/bin.
228
455
chown and chmod the installed trampoline.
229
456
Copy www/ to $target.
230
457
Copy jail/ to jails template directory (unless --nojail specified).
231
 
 
232
 
--nojail    Do not copy the jail.
 
458
Copy subjects/ to subjects directory (unless --nosubjects specified).
 
459
 
 
460
--nojail        Do not copy the jail.
 
461
--nosubjects    Do not copy the subjects and problems directories.
 
462
--dry | -n  Print out the actions but don't do anything."""
 
463
    elif operation == 'updatejails':
 
464
        print """sudo python setup.py updatejails [--dry|-n]
 
465
(Requires root)
 
466
Copy jail/ to each subdirectory in jails directory.
 
467
 
233
468
--dry | -n  Print out the actions but don't do anything."""
234
469
    else:
235
470
        print >>sys.stderr, (
240
475
def listmake(args):
241
476
    # We build two separate lists, by walking www and console
242
477
    list_www = build_list_py_files('www')
243
 
    list_console = build_list_py_files('console')
 
478
    list_lib = build_list_py_files('lib')
 
479
    list_subjects = build_list_py_files('subjects', no_top_level=True)
 
480
    list_problems = build_list_py_files('problems', no_top_level=True)
 
481
    list_scripts = [
 
482
        "scripts/python-console",
 
483
        "scripts/fileservice",
 
484
        "scripts/usrmgt-server",
 
485
    ]
244
486
    # Make sure that the files generated by conf are in the list
245
487
    # (since listmake is typically run before conf)
246
 
    if "www/conf/conf.py" not in list_www:
247
 
        list_www.append("www/conf/conf.py")
248
 
    # Make sure that console/python-console is in the list
249
 
    if "console/python-console" not in list_console:
250
 
        list_console.append("console/python-console")
 
488
    if "lib/conf/conf.py" not in list_lib:
 
489
        list_lib.append("lib/conf/conf.py")
251
490
    # Write these out to a file
252
491
    cwd = os.getcwd()
253
492
    # the files that will be created/overwritten
267
506
list_www = """)
268
507
        writelist_pretty(file, list_www)
269
508
        file.write("""
270
 
# List of all installable files in console directory.
271
 
list_console = """)
272
 
        writelist_pretty(file, list_console)
 
509
# List of all installable files in lib directory.
 
510
list_lib = """)
 
511
        writelist_pretty(file, list_lib)
 
512
        file.write("""
 
513
# List of all installable files in scripts directory.
 
514
list_scripts = """)
 
515
        writelist_pretty(file, list_scripts)
 
516
        file.write("""
 
517
# List of all installable files in subjects directory.
 
518
# This is to install sample subjects and material.
 
519
list_subjects = """)
 
520
        writelist_pretty(file, list_subjects)
 
521
        file.write("""
 
522
# List of all installable files in problems directory.
 
523
# This is to install sample exercise material.
 
524
list_problems = """)
 
525
        writelist_pretty(file, list_problems)
273
526
 
274
527
        file.close()
275
528
    except IOError, (errno, strerror):
286
539
 
287
540
    return 0
288
541
 
289
 
def build_list_py_files(dir):
 
542
def build_list_py_files(dir, no_top_level=False):
290
543
    """Builds a list of all py files found in a directory and its
291
 
    subdirectories. Returns this as a list of strings."""
 
544
    subdirectories. Returns this as a list of strings.
 
545
    no_top_level=True means the file paths will not include the top-level
 
546
    directory.
 
547
    """
292
548
    pylist = []
293
549
    for (dirpath, dirnames, filenames) in os.walk(dir):
294
550
        # Exclude directories beginning with a '.' (such as '.svn')
296
552
        # All *.py files are added to the list
297
553
        pylist += [os.path.join(dirpath, item) for item in filenames
298
554
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
555
    if no_top_level:
 
556
        for i in range(0, len(pylist)):
 
557
            _, pylist[i] = pylist[i].split(os.sep, 1)
299
558
    return pylist
300
559
 
301
560
def writelist_pretty(file, list):
309
568
        file.write(']\n')
310
569
 
311
570
def conf(args):
312
 
    global root_dir, ivle_install_dir, jail_base, allowed_uids
 
571
    global db_port, usrmgt_port
313
572
    # Set up some variables
314
573
 
315
574
    cwd = os.getcwd()
316
575
    # the files that will be created/overwritten
317
 
    conffile = os.path.join(cwd, "www/conf/conf.py")
 
576
    conffile = os.path.join(cwd, "lib/conf/conf.py")
 
577
    jailconffile = os.path.join(cwd, "lib/conf/jailconf.py")
318
578
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
319
579
 
320
580
    # Get command-line arguments to avoid asking questions.
321
581
 
322
 
    (opts, args) = getopt.gnu_getopt(args, "", ['root_dir=',
323
 
                    'ivle_install_dir=', 'jail_base=', 'allowed_uids='])
 
582
    optnames = []
 
583
    for opt in config_options:
 
584
        optnames.append(opt.option_name + "=")
 
585
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
324
586
 
325
587
    if args != []:
326
588
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
332
594
        print """This tool will create the following files:
333
595
    %s
334
596
    %s
 
597
    %s
335
598
prompting you for details about your configuration. The file will be
336
599
overwritten if it already exists. It will *not* install or deploy IVLE.
337
600
 
338
601
Please hit Ctrl+C now if you do not wish to do this.
339
 
""" % (conffile, conf_hfile)
 
602
""" % (conffile, jailconffile, conf_hfile)
340
603
 
341
604
        # Get information from the administrator
342
605
        # If EOF is encountered at any time during the questioning, just exit
343
606
        # silently
344
607
 
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 '
349
 
        'system):')
350
 
        jail_base = query_user(jail_base,
351
 
        """Root directory where the jails (containing user files) are stored
352
 
(on the local file system):""")
353
 
        allowed_uids = query_user(allowed_uids,
354
 
        """UID of the web server process which will run IVLE.
355
 
Only this user may execute the trampoline. May specify multiple users as
356
 
a comma-separated list.
357
 
    (eg. "1002,78")""")
358
 
 
 
608
        for opt in config_options:
 
609
            globals()[opt.option_name] = \
 
610
                query_user(globals()[opt.option_name], opt.prompt)
359
611
    else:
360
612
        opts = dict(opts)
361
613
        # 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']
 
614
        for opt in config_options:
 
615
            if '--' + opt.option_name in opts:
 
616
                globals()[opt.option_name] = opts['--' + opt.option_name]
370
617
 
371
618
    # Error handling on input values
372
619
    try:
373
 
        allowed_uids = map(int, allowed_uids.split(','))
 
620
        allowed_uids_list = map(int, allowed_uids.split(','))
374
621
    except ValueError:
375
622
        print >>sys.stderr, (
376
623
        "Invalid UID list (%s).\n"
377
624
        "Must be a comma-separated list of integers." % allowed_uids)
378
625
        return 1
 
626
    try:
 
627
        db_port = int(db_port)
 
628
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
629
    except ValueError:
 
630
        print >>sys.stderr, (
 
631
        "Invalid DB port (%s).\n"
 
632
        "Must be an integer between 0 and 65535." % repr(db_port))
 
633
        return 1
 
634
    try:
 
635
        usrmgt_port = int(usrmgt_port)
 
636
        if usrmgt_port < 0 or usrmgt_port >= 65536: raise ValueError()
 
637
    except ValueError:
 
638
        print >>sys.stderr, (
 
639
        "Invalid user management port (%s).\n"
 
640
        "Must be an integer between 0 and 65535." % repr(usrmgt_port))
 
641
        return 1
379
642
 
380
 
    # Write www/conf/conf.py
 
643
    # Write lib/conf/conf.py
381
644
 
382
645
    try:
383
646
        conf = open(conffile, "w")
386
649
# conf.py
387
650
# Miscellaneous application settings
388
651
 
 
652
""")
 
653
        for opt in config_options:
 
654
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
655
                repr(globals()[opt.option_name])))
 
656
 
 
657
        conf.close()
 
658
    except IOError, (errno, strerror):
 
659
        print "IO error(%s): %s" % (errno, strerror)
 
660
        sys.exit(1)
 
661
 
 
662
    print "Successfully wrote lib/conf/conf.py"
 
663
 
 
664
    # Write conf/jailconf.py
 
665
 
 
666
    try:
 
667
        conf = open(jailconffile, "w")
 
668
 
 
669
        # In the "in-jail" version of conf, we don't need MOST of the details
 
670
        # (it would be a security risk to have them here).
 
671
        # So we just write root_dir, and jail_base is "/".
 
672
        # (jail_base being "/" means "jail-relative" paths are relative to "/"
 
673
        # when inside the jail.)
 
674
        conf.write("""# IVLE Configuration File
 
675
# conf.py
 
676
# Miscellaneous application settings
 
677
# (User jail version)
 
678
 
389
679
 
390
680
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
391
681
# with this).
392
682
# eg. "/" or "/ivle".
393
 
root_dir = "%s"
394
 
 
395
 
# In the local file system, where IVLE is actually installed.
396
 
# This directory should contain the "www" and "bin" directories.
397
 
ivle_install_dir = "%s"
 
683
root_dir = %s
398
684
 
399
685
# In the local file system, where are the student/user file spaces located.
400
686
# The user jails are expected to be located immediately in subdirectories of
401
687
# this location.
402
 
jail_base = "%s"
403
 
""" % (root_dir, ivle_install_dir, jail_base))
 
688
jail_base = '/'
 
689
 
 
690
# The hostname for serving publicly accessible pages
 
691
public_host = %s
 
692
""" % (repr(root_dir),repr(public_host)))
404
693
 
405
694
        conf.close()
406
695
    except IOError, (errno, strerror):
407
696
        print "IO error(%s): %s" % (errno, strerror)
408
697
        sys.exit(1)
409
698
 
410
 
    print "Successfully wrote www/conf/conf.py"
 
699
    print "Successfully wrote lib/conf/jailconf.py"
411
700
 
412
701
    # Write trampoline/conf.h
413
702
 
432
721
 * (Note that root is an implicit member of this list).
433
722
 */
434
723
static const int allowed_uids[] = { %s };
435
 
""" % (jail_base, repr(allowed_uids)[1:-1]))
 
724
""" % (repr(jail_base)[1:-1], repr(allowed_uids_list)[1:-1]))
 
725
    # Note: The above uses PYTHON reprs, not C reprs
 
726
    # However they should be the same with the exception of the outer
 
727
    # characters, which are stripped off and replaced
436
728
 
437
729
        conf.close()
438
730
    except IOError, (errno, strerror):
444
736
    print
445
737
    print "You may modify the configuration at any time by editing"
446
738
    print conffile
 
739
    print jailconffile
447
740
    print conf_hfile
448
741
    print
449
742
    return 0
458
751
        print "Dry run (no actions will be executed\n"
459
752
 
460
753
    # Compile the trampoline
461
 
    action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
462
 
        'trampoline/trampoline.c'], dry)
 
754
    curdir = os.getcwd()
 
755
    os.chdir('trampoline')
 
756
    action_runprog('make', [], dry)
 
757
    os.chdir(curdir)
463
758
 
464
759
    # Create the jail and its subdirectories
465
760
    # Note: Other subdirs will be made by copying files
468
763
    action_mkdir('jail/tmp', dry)
469
764
 
470
765
    # Copy all console and operating system files into the jail
471
 
    action_copylist(install_list.list_console, 'jail/opt/ivle', dry)
 
766
    action_copylist(install_list.list_scripts, 'jail/opt/ivle', dry)
472
767
    copy_os_files_jail(dry)
 
768
    # Chmod the python console
 
769
    action_chmod_x('jail/opt/ivle/scripts/python-console', dry)
 
770
    action_chmod_x('jail/opt/ivle/scripts/fileservice', dry)
 
771
    
 
772
    # Also copy the IVLE lib directory into the jail
 
773
    # This is necessary for running certain scripts
 
774
    action_copylist(install_list.list_lib, 'jail/opt/ivle', dry)
 
775
    # IMPORTANT: The file jail/opt/ivle/lib/conf/conf.py contains details
 
776
    # which could compromise security if left in the jail (such as the DB
 
777
    # password).
 
778
    # The "safe" version is in jailconf.py. Delete conf.py and replace it with
 
779
    # jailconf.py.
 
780
    action_copyfile('lib/conf/jailconf.py',
 
781
        'jail/opt/ivle/lib/conf/conf.py', dry)
473
782
 
474
783
    # Compile .py files into .pyc or .pyo files
475
784
    compileall.compile_dir('www', quiet=True)
476
 
    compileall.compile_dir('console', quiet=True)
 
785
    compileall.compile_dir('lib', quiet=True)
 
786
    compileall.compile_dir('scripts', quiet=True)
 
787
    compileall.compile_dir('jail/opt/ivle/lib', quiet=True)
 
788
 
 
789
    # Set up ivle.pth inside the jail
 
790
    # Need to set /opt/ivle/lib to be on the import path
 
791
    ivle_pth = \
 
792
        "jail/usr/lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION
 
793
    f = open(ivle_pth, 'w')
 
794
    f.write('/opt/ivle/lib\n')
 
795
    f.close()
477
796
 
478
797
    return 0
479
798
 
481
800
    """Copies necessary Operating System files from their usual locations
482
801
    into the jail/ directory of the cwd."""
483
802
    # 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)
 
803
    for filename in JAIL_FILES:
 
804
        copy_file_to_jail(filename, dry)
 
805
    for src, dst in JAIL_LINKS.items():
 
806
        action_symlink(src, dst, dry)
 
807
    for src, dst in JAIL_COPYTREES.items():
 
808
        action_copytree(src, dst, dry)
493
809
 
494
810
def copy_file_to_jail(src, dry):
495
811
    """Copies a single file from an absolute location into the same location
499
815
 
500
816
def install(args):
501
817
    # Get "dry" and "nojail" variables from command line
502
 
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry', 'nojail'])
 
818
    (opts, args) = getopt.gnu_getopt(args, "n",
 
819
        ['dry', 'nojail', 'nosubjects'])
503
820
    opts = dict(opts)
504
821
    dry = '-n' in opts or '--dry' in opts
505
822
    nojail = '--nojail' in opts
 
823
    nosubjects = '--nosubjects' in opts
506
824
 
507
825
    if dry:
508
826
        print "Dry run (no actions will be executed\n"
522
840
    # chown trampoline to root and set setuid bit
523
841
    action_chown_setuid(tramppath, dry)
524
842
 
525
 
    # Copy the www directory using the list
 
843
    # Copy the www and lib directories using the list
526
844
    action_copylist(install_list.list_www, ivle_install_dir, dry)
 
845
    action_copylist(install_list.list_lib, ivle_install_dir, dry)
527
846
 
528
847
    if not nojail:
529
848
        # Copy the local jail directory built by the build action
530
849
        # to the jails template directory (it will be used as a template
531
850
        # for all the students' jails).
532
851
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
852
    if not nosubjects:
 
853
        # Copy the subjects and problems directories across
 
854
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
855
            srcdir="./subjects")
 
856
        action_copylist(install_list.list_problems, problems_base, dry,
 
857
            srcdir="./problems")
 
858
 
 
859
    # Append IVLE path to ivle.pth in python site packages
 
860
    # (Unless it's already there)
 
861
    ivle_pth = os.path.join(sys.prefix,
 
862
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
863
    ivle_www = os.path.join(ivle_install_dir, "www")
 
864
    ivle_lib = os.path.join(ivle_install_dir, "lib")
 
865
    write_ivle_pth = True
 
866
    write_ivle_lib_pth = True
 
867
    try:
 
868
        file = open(ivle_pth, 'r')
 
869
        for line in file:
 
870
            if line.strip() == ivle_www:
 
871
                write_ivle_pth = False
 
872
            elif line.strip() == ivle_lib:
 
873
                write_ivle_lib_pth = False
 
874
        file.close()
 
875
    except (IOError, OSError):
 
876
        pass
 
877
    if write_ivle_pth:
 
878
        action_append(ivle_pth, ivle_www)
 
879
    if write_ivle_lib_pth:
 
880
        action_append(ivle_pth, ivle_lib)
 
881
 
 
882
    return 0
 
883
 
 
884
def updatejails(args):
 
885
    # Get "dry" variable from command line
 
886
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
887
    opts = dict(opts)
 
888
    dry = '-n' in opts or '--dry' in opts
 
889
 
 
890
    if dry:
 
891
        print "Dry run (no actions will be executed\n"
 
892
 
 
893
    if not dry and os.geteuid() != 0:
 
894
        print >>sys.stderr, "Must be root to run install"
 
895
        print >>sys.stderr, "(I need to chown some files)."
 
896
        return 1
 
897
 
 
898
    # Update the template jail directory in case it hasn't been installed
 
899
    # recently.
 
900
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
901
 
 
902
    # Re-link all the files in all students jails.
 
903
    for dir in os.listdir(jail_base):
 
904
        if dir == 'template': continue
 
905
        # First back up the student's home directory
 
906
        temp_home = os.tmpnam()
 
907
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
908
        # Delete the student's jail and relink the jail files
 
909
        action_linktree(os.path.join(jail_base, 'template'),
 
910
            os.path.join(jail_base, dir), dry)
 
911
        # Restore the student's home directory
 
912
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
913
        # Set up the user's home directory just in case they don't have a
 
914
        # directory for this yet
 
915
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
533
916
 
534
917
    return 0
535
918
 
562
945
    if ret != 0:
563
946
        raise RunError(prog, ret)
564
947
 
 
948
def action_remove(path, dry):
 
949
    """Calls rmtree, deleting the target file if it exists."""
 
950
    try:
 
951
        print "rm -r", path
 
952
        if not dry:
 
953
            shutil.rmtree(path, True)
 
954
    except OSError, (err, msg):
 
955
        if err != errno.EEXIST:
 
956
            raise
 
957
        # Otherwise, didn't exist, so we don't care
 
958
 
 
959
def action_rename(src, dst, dry):
 
960
    """Calls rename. Deletes the target if it already exists."""
 
961
    action_remove(dst, dry)
 
962
    print "mv ", src, dst
 
963
    if dry: return
 
964
    try:
 
965
        os.rename(src, dst)
 
966
    except OSError, (err, msg):
 
967
        if err != errno.EEXIST:
 
968
            raise
 
969
 
565
970
def action_mkdir(path, dry):
566
971
    """Calls mkdir. Silently ignored if the directory already exists.
567
972
    Creates all parent directories as necessary."""
579
984
    directories as necessary.
580
985
 
581
986
    See shutil.copytree."""
582
 
    if os.access(dst, os.F_OK):
583
 
        print "rm -r", dst
584
 
        if not dry:
585
 
            shutil.rmtree(dst, True)
 
987
    action_remove(dst, dry)
586
988
    print "cp -r", src, dst
587
989
    if dry: return
588
990
    shutil.copytree(src, dst, True)
589
991
 
590
 
def action_copylist(srclist, dst, dry):
 
992
def action_linktree(src, dst, dry):
 
993
    """Hard-links an entire directory tree. Same as copytree but the created
 
994
    files are hard-links not actual copies. Removes the existing destination.
 
995
    """
 
996
    action_remove(dst, dry)
 
997
    print "<cp with hardlinks> -r", src, dst
 
998
    if dry: return
 
999
    common.makeuser.linktree(src, dst)
 
1000
 
 
1001
def action_copylist(srclist, dst, dry, srcdir="."):
591
1002
    """Copies all files in a list to a new location. The files in the list
592
1003
    are read relative to the current directory, and their destinations are the
593
1004
    same paths relative to dst. Creates all parent directories as necessary.
 
1005
    srcdir is "." by default, can be overridden.
594
1006
    """
595
1007
    for srcfile in srclist:
596
1008
        dstfile = os.path.join(dst, srcfile)
 
1009
        srcfile = os.path.join(srcdir, srcfile)
597
1010
        dstdir = os.path.split(dstfile)[0]
598
1011
        if not os.path.isdir(dstdir):
599
1012
            action_mkdir(dstdir, dry)
608
1021
def action_copyfile(src, dst, dry):
609
1022
    """Copies one file to a new location. Creates all parent directories
610
1023
    as necessary.
 
1024
    Warn if file not found.
611
1025
    """
612
1026
    dstdir = os.path.split(dst)[0]
613
1027
    if not os.path.isdir(dstdir):
617
1031
        try:
618
1032
            shutil.copyfile(src, dst)
619
1033
            shutil.copymode(src, dst)
620
 
        except shutil.Error:
621
 
            pass
 
1034
        except (shutil.Error, IOError), e:
 
1035
            print "Warning: " + str(e)
622
1036
 
623
1037
def action_symlink(src, dst, dry):
624
1038
    """Creates a symlink in a given location. Creates all parent directories
634
1048
    if not dry:
635
1049
        os.symlink(src, dst)
636
1050
 
 
1051
def action_append(ivle_pth, ivle_www):
 
1052
    file = open(ivle_pth, 'a+')
 
1053
    file.write(ivle_www + '\n')
 
1054
    file.close()
 
1055
 
637
1056
def action_chown_setuid(file, dry):
638
1057
    """Chowns a file to root, and sets the setuid bit on the file.
639
1058
    Calling this function requires the euid to be root.
648
1067
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
649
1068
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
650
1069
 
 
1070
def action_chmod_x(file, dry):
 
1071
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
1072
    print "chmod 755", file
 
1073
    if not dry:
 
1074
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
1075
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
1076
 
651
1077
def query_user(default, prompt):
652
1078
    """Prompts the user for a string, which is read from a line of stdin.
653
1079
    Exits silently if EOF is encountered. Returns the string, with spaces