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

1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
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.
8
#
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.
13
#
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
17
18
# Module: setup/util
19
# Author: Matt Giuca, Refactored by David Coles
20
# Date:   02/07/2008
21
22
# setup/util.py
23
# Contains a set of functions useful for the setup program.
24
25
import os
26
import shutil
27
import errno
28
import sys
29
import string
30
import stat
31
import optparse
32
import mimetypes
33
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',
1099.1.171 by William Grant
Drop www/ stuff from setup.
37
           'action_symlink', 'action_chown',
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
38
           'action_chown_setuid', 'action_chmod_x', 'action_make_private',
1099.1.171 by William Grant
Drop www/ stuff from setup.
39
           'filter_mutate', 'get_svn_revision', 'InstallList',
1092.1.1 by William Grant
[Uber-commit of holiday work because I lacked a local copy of the branch.]
40
           'make_install_path', 'wwwuid']
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
41
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]
46
47
# Location of standard programs
48
RSYNC = '/usr/bin/rsync'
49
50
# UID of the Webserver
51
wwwuid = 33
52
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)
58
59
# The actions call Python os functions but print actions and handle dryness.
60
# May still throw os exceptions if errors occur.
61
62
class RunError:
63
    """Represents an error when running a program (nonzero return)."""
64
    def __init__(self, prog, retcode):
65
        self.prog = prog
66
        self.retcode = retcode
67
    def __str__(self):
68
        return str(self.prog) + " returned " + repr(self.retcode)
69
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.
74
75
    Throws a RunError with a retcode of the return value of the program,
76
    if the program did not return 0.
77
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.
82
    """
83
    print prog, string.join(args, ' ')
84
    if dry: return
85
    ret = os.spawnvp(os.P_WAIT, prog, [prog] + args)
86
    if ret != 0:
87
        raise RunError(prog, ret)
88
89
def action_remove(path, dry):
90
    """Calls rmtree, deleting the target file if it exists."""
91
    try:
92
        print "rm -r", path
93
        if not dry:
94
            shutil.rmtree(path, True)
95
    except OSError, (err, msg):
96
        if err != errno.EEXIST:
97
            raise
98
        # Otherwise, didn't exist, so we don't care
99
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
104
    if dry: return
105
    try:
106
        os.rename(src, dst)
107
    except OSError, (err, msg):
108
        if err != errno.EEXIST:
109
            raise
110
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
115
    if dry: return
116
    try:
117
        os.makedirs(path)
118
    except OSError, (err, msg):
119
        if err != errno.EEXIST:
120
            raise
121
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.
126
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))):
131
        return
132
    
133
    # Try to do the copy with rsync, if that fails just copy
134
    try:
135
        action_runprog(RSYNC, ['-a','--delete',src + '/',dst], dry)
136
    except RunError:
137
        if dry: return
138
        action_remove(dst, dry)
139
        print "cp -r", src, dst
140
        shutil.copytree(src, dst, True)
141
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.
147
148
    If onlybasename is True, only the basename of the source is appended to
149
    the destination.
150
    """
151
    for srcfile in srclist:
152
        if onlybasename:
153
            dstfile = os.path.join(dst, os.path.basename(srcfile))
154
        else:
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
161
        if not dry:
162
            try:
163
                shutil.copyfile(srcfile, dstfile)
164
                shutil.copymode(srcfile, dstfile)
165
            except shutil.Error:
166
                pass
167
168
def action_copyfile(src, dst, dry):
169
    """Copies one file to a new location. Creates all parent directories
170
    as necessary.
171
    Warn if file not found.
172
    """
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
177
    if not dry:
178
        try:
179
            shutil.copyfile(src, dst)
180
            shutil.copymode(src, dst)
181
        except (shutil.Error, IOError), e:
182
            print "Warning: " + str(e)
183
184
def action_symlink(src, dst, dry):
185
    """Creates a symlink in a given location. Creates all parent directories
186
    as necessary.
187
    """
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):
193
        os.remove(dst)
194
    print "ln -fs", src, dst
195
    if not dry:
196
        os.symlink(src, dst)
197
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
202
    """
203
    print "chown root:root", file
204
    if not dry:
205
        os.chown(file, 0, 0)
206
    print "chmod a+xs", file
207
    print "chmod u+rw", file
208
    if not dry:
209
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
210
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
211
212
def action_chmod_x(file, dry):
213
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
214
    print "chmod 755", file
215
    if not dry:
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)
218
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)
222
    if not dry:
223
        os.chown(file, uid, gid)
224
225
def action_make_private(file, dry):
226
    """Ensures that a file is private to IVLE (chowns to www-data and chmod to 
227
    600)"""
228
    action_chown(file, wwwuid, wwwuid, dry)
229
    print "chmod 600", file
230
    if not dry:
231
        os.chmod(file, stat.S_IRUSR | stat.S_IWUSR)
232
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."""
236
    i = len(list)-1
237
    while i >= 0:
238
        # Delete elements which do not match
239
        if not function(list[i]):
240
            del list[i]
241
        i -= 1
242
243
def get_svn_revision():
244
    """Returns either the current SVN revision of this build, or None"""
245
    import pysvn
246
    try:
247
        svn = pysvn.Client()
248
        entry = svn.info('.')
249
        revnum = entry.revision.number
250
    except pysvn.ClientError, e:
251
        revnum = None
252
    return revnum
253
254
### InstallList and helpers ###
255
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',
1099.1.225 by Nick Chadwick
Modified the setup script to include '.txt' files.
259
    'text/css', 'image/png', 'image/gif', 'application/xml', 'text/plain']
1092.1.15 by Matt Giuca
Added config validation spec: ivle/config/ivle-spec.conf.
260
# Filenames which will automatically be placed in the list by InstallList.
261
whitelist_filenames = ['ivle-spec.conf']
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
262
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
267
    directory.
268
    """
269
    pylist = []
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
1092.1.15 by Matt Giuca
Added config validation spec: ivle/config/ivle-spec.conf.
275
            if mimetypes.guess_type(item)[0] in installlist_mimetypes or
276
               item in whitelist_filenames]
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
277
    if no_top_level:
278
        for i in range(0, len(pylist)):
279
            _, pylist[i] = pylist[i].split(os.sep, 1)
280
    return pylist
281
1092.1.1 by William Grant
[Uber-commit of holiday work because I lacked a local copy of the branch.]
282
def make_install_path(rootdir, path):
283
    '''Combine an installation root directory and final install path.
284
285
    Normalises path, and joins it to the end of rootdir, removing the leading
286
    / to make it relative if required.
287
    '''
288
    normpath = os.path.normpath(path)
289
    if normpath.startswith(os.sep):
290
        normpath = normpath[1:]
291
    return os.path.join(rootdir, normpath)
292
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
293
class InstallList(object):
294
    list_ivle_lib = property(lambda self: build_list_py_files('ivle'))
295
296
    list_subjects = property(lambda self: build_list_py_files('subjects',
297
                             no_top_level=True))
298
299
    list_exercises = property(lambda self: build_list_py_files('exercises',
300
                              no_top_level=True))
301
302
    list_services = [
303
        "services/python-console",
304
        "services/fileservice",
305
        "services/serveservice",
306
        "services/usrmgt-server",
307
        "services/diffservice",
308
        "services/svnlogservice",
1092.1.1 by William Grant
[Uber-commit of holiday work because I lacked a local copy of the branch.]
309
        "services/usrmgt-server", # XXX: Should be in bin/
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
310
    ]
311
312
    list_user_binaries = [
313
        "bin/ivle-enrol",
314
        "bin/ivle-enrolallusers",
315
        "bin/ivle-listusers",
316
        "bin/ivle-makeuser",
317
        "bin/ivle-marks",
318
        "bin/ivle-mountallusers",
319
        "bin/ivle-remakeuser",
320
        "bin/ivle-showenrolment",
1092.1.55 by William Grant
Install ivle-config.
321
        "bin/ivle-config",
1092.1.58 by William Grant
Add an ivle-createdatadirs script, to create the hierarchy under /var/lib/ivle.
322
        "bin/ivle-createdatadirs",
1092.1.1 by William Grant
[Uber-commit of holiday work because I lacked a local copy of the branch.]
323
        "bin/ivle-buildjail",
1108 by William Grant
Add ivle-cloneworksheets, which copies worksheets between offerings.
324
        "bin/ivle-addexercise",
325
        "bin/ivle-cloneworksheets",
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
326
    ]