1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2008 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
# Author: Matt Giuca, Refactored by David Coles
23
# Contains a set of functions useful for the setup program.
34
__all__ = ['PYTHON_VERSION', 'copy_file_to_jail', 'RunError',
35
'action_runprog', 'action_remove', 'action_rename', 'action_mkdir',
36
'action_copytree', 'action_copylist', 'action_copyfile',
37
'action_symlink', 'action_chown',
38
'action_chown_setuid', 'action_chmod_x', 'action_make_private',
39
'filter_mutate', 'get_svn_revision', 'InstallList',
40
'make_install_path', 'wwwuid']
42
# Determine which Python version (2.4 or 2.5, for example) we are running,
43
# and use that as the filename to the Python directory.
44
# Just get the first 3 characters of sys.version.
45
PYTHON_VERSION = sys.version[0:3]
47
# Location of standard programs
48
RSYNC = '/usr/bin/rsync'
50
# UID of the Webserver
53
def copy_file_to_jail(src, dry):
54
"""Copies a single file from an absolute location into the same location
55
within the jail. src must begin with a '/'. The jail will be located
56
in a 'jail' subdirectory of the current path."""
57
action_copyfile(src, 'jail' + src, dry)
59
# The actions call Python os functions but print actions and handle dryness.
60
# May still throw os exceptions if errors occur.
63
"""Represents an error when running a program (nonzero return)."""
64
def __init__(self, prog, retcode):
66
self.retcode = retcode
68
return str(self.prog) + " returned " + repr(self.retcode)
70
def action_runprog(prog, args, dry):
71
"""Runs a unix program. Searches in $PATH. Synchronous (waits for the
72
program to return). Runs in the current environment. First prints the
73
action as a "bash" line.
75
Throws a RunError with a retcode of the return value of the program,
76
if the program did not return 0.
78
prog: String. Name of the program. (No path required, if in $PATH).
79
args: [String]. Arguments to the program. (Note, this does not allow you to
80
set argv[0]; it will always be prog.)
81
dry: Bool. If True, prints but does not execute.
83
print prog, string.join(args, ' ')
85
ret = os.spawnvp(os.P_WAIT, prog, [prog] + args)
87
raise RunError(prog, ret)
89
def action_remove(path, dry):
90
"""Calls rmtree, deleting the target file if it exists."""
94
shutil.rmtree(path, True)
95
except OSError, (err, msg):
96
if err != errno.EEXIST:
98
# Otherwise, didn't exist, so we don't care
100
def action_rename(src, dst, dry):
101
"""Calls rename. Deletes the target if it already exists."""
102
action_remove(dst, dry)
103
print "mv ", src, dst
107
except OSError, (err, msg):
108
if err != errno.EEXIST:
111
def action_mkdir(path, dry):
112
"""Calls mkdir. Silently ignored if the directory already exists.
113
Creates all parent directories as necessary."""
114
print "mkdir -p", path
118
except OSError, (err, msg):
119
if err != errno.EEXIST:
122
def action_copytree(src, dst, dry):
123
"""Copies an entire directory tree. Symlinks are seen as normal files and
124
copies of the entire file (not the link) are made. Creates all parent
125
directories as necessary.
127
See shutil.copytree."""
128
# Allow copying over itself
129
if (os.path.normpath(os.path.join(os.getcwd(),src)) ==
130
os.path.normpath(os.path.join(os.getcwd(),dst))):
133
# Try to do the copy with rsync, if that fails just copy
135
action_runprog(RSYNC, ['-a','--delete',src + '/',dst], dry)
138
action_remove(dst, dry)
139
print "cp -r", src, dst
140
shutil.copytree(src, dst, True)
142
def action_copylist(srclist, dst, dry, srcdir=".", onlybasename=False):
143
"""Copies all files in a list to a new location. The files in the list
144
are read relative to the current directory, and their destinations are the
145
same paths relative to dst. Creates all parent directories as necessary.
146
srcdir is "." by default, can be overridden.
148
If onlybasename is True, only the basename of the source is appended to
151
for srcfile in srclist:
153
dstfile = os.path.join(dst, os.path.basename(srcfile))
155
dstfile = os.path.join(dst, srcfile)
156
srcfile = os.path.join(srcdir, srcfile)
157
dstdir = os.path.split(dstfile)[0]
158
if not os.path.isdir(dstdir):
159
action_mkdir(dstdir, dry)
160
print "cp -f", srcfile, dstfile
163
shutil.copyfile(srcfile, dstfile)
164
shutil.copymode(srcfile, dstfile)
168
def action_copyfile(src, dst, dry):
169
"""Copies one file to a new location. Creates all parent directories
171
Warn if file not found.
173
dstdir = os.path.split(dst)[0]
174
if not os.path.isdir(dstdir):
175
action_mkdir(dstdir, dry)
176
print "cp -f", src, dst
179
shutil.copyfile(src, dst)
180
shutil.copymode(src, dst)
181
except (shutil.Error, IOError), e:
182
print "Warning: " + str(e)
184
def action_symlink(src, dst, dry):
185
"""Creates a symlink in a given location. Creates all parent directories
188
dstdir = os.path.split(dst)[0]
189
if not os.path.isdir(dstdir):
190
action_mkdir(dstdir, dry)
191
# Delete existing file
192
if os.path.exists(dst):
194
print "ln -fs", src, dst
198
def action_chown_setuid(file, dry):
199
"""Chowns a file to root, and sets the setuid bit on the file.
200
Calling this function requires the euid to be root.
201
The actual mode of path is set to: rws--s--s
203
print "chown root:root", file
206
print "chmod a+xs", file
207
print "chmod u+rw", file
209
os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
210
| stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
212
def action_chmod_x(file, dry):
213
"""Chmod 755 a file (sets permissions to rwxr-xr-x)."""
214
print "chmod 755", file
216
os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
217
| stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
219
def action_chown(file, uid, gid, dry):
220
"""Chowns a file to the specified numeric UID and GID."""
221
print "chown %s:%s %s"%(uid, gid, file)
223
os.chown(file, uid, gid)
225
def action_make_private(file, dry):
226
"""Ensures that a file is private to IVLE (chowns to www-data and chmod to
228
action_chown(file, wwwuid, wwwuid, dry)
229
print "chmod 600", file
231
os.chmod(file, stat.S_IRUSR | stat.S_IWUSR)
233
def filter_mutate(function, list):
234
"""Like built-in filter, but mutates the given list instead of returning a
235
new one. Returns None."""
238
# Delete elements which do not match
239
if not function(list[i]):
243
def get_svn_revision():
244
"""Returns either the current SVN revision of this build, or None"""
248
entry = svn.info('.')
249
revnum = entry.revision.number
250
except pysvn.ClientError, e:
254
### InstallList and helpers ###
256
# Mime types which will automatically be placed in the list by InstallList.
257
installlist_mimetypes = ['text/x-python', 'text/html',
258
'application/x-javascript', 'application/javascript',
259
'text/css', 'image/png', 'image/gif', 'application/xml', 'text/plain']
260
# Filenames which will automatically be placed in the list by InstallList.
261
whitelist_filenames = ['ivle-spec.conf']
263
def build_list_py_files(dir, no_top_level=False):
264
"""Builds a list of all py files found in a directory and its
265
subdirectories. Returns this as a list of strings.
266
no_top_level=True means the file paths will not include the top-level
270
for (dirpath, dirnames, filenames) in os.walk(dir):
271
# Exclude directories beginning with a '.' (such as '.svn')
272
filter_mutate(lambda x: x[0] != '.', dirnames)
273
# All *.py files are added to the list
274
pylist += [os.path.join(dirpath, item) for item in filenames
275
if mimetypes.guess_type(item)[0] in installlist_mimetypes or
276
item in whitelist_filenames]
278
for i in range(0, len(pylist)):
279
_, pylist[i] = pylist[i].split(os.sep, 1)
282
def make_install_path(rootdir, path):
283
'''Combine an installation root directory and final install path.
285
Normalises path, and joins it to the end of rootdir, removing the leading
286
/ to make it relative if required.
288
normpath = os.path.normpath(path)
289
if normpath.startswith(os.sep):
290
normpath = normpath[1:]
291
return os.path.join(rootdir, normpath)
293
class InstallList(object):
294
list_ivle_lib = property(lambda self: build_list_py_files('ivle'))
296
list_subjects = property(lambda self: build_list_py_files('subjects',
299
list_exercises = property(lambda self: build_list_py_files('exercises',
303
"services/python-console",
304
"services/fileservice",
305
"services/serveservice",
306
"services/usrmgt-server",
307
"services/diffservice",
308
"services/svnlogservice",
309
"services/usrmgt-server", # XXX: Should be in bin/
312
list_user_binaries = [
313
"bin/ivle-addexercise",
315
"bin/ivle-buildjail",
316
"bin/ivle-cloneworksheets",
318
"bin/ivle-createdatadirs",
320
"bin/ivle-enrolallusers",
321
"bin/ivle-fetchsubmissions",
322
"bin/ivle-listusers",
323
"bin/ivle-loadsampledata",
325
"bin/ivle-mountallusers",
326
"bin/ivle-refreshfilesystem",
327
"bin/ivle-remakeuser",
328
"bin/ivle-showenrolment",