20b61668e9331d7348ef34b36f3413375820b22e
[chopwood] / agpl.py
1 ### -*-python-*-
2 ###
3 ### GNU Affero General Public License compliance
4 ###
5 ### (c) 2013 Mark Wooding
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This file is part of Chopwood: a password-changing service.
11 ###
12 ### Chopwood is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU Affero General Public License as
14 ### published by the Free Software Foundation; either version 3 of the
15 ### License, or (at your option) any later version.
16 ###
17 ### Chopwood is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ### GNU Affero General Public License for more details.
21 ###
22 ### You should have received a copy of the GNU Affero General Public
23 ### License along with Chopwood; if not, see
24 ### <http://www.gnu.org/licenses/>.
25
26 import contextlib as CTX
27 import grp as GR
28 import os as OS
29 import pwd as PW
30 import shlex as SL
31 import shutil as SH
32 import subprocess as SUB
33 import sys as SYS
34 import tarfile as TAR
35 import tempfile as TF
36 import time as T
37
38 from cStringIO import StringIO
39
40 from auto import PACKAGE, VERSION
41 import util as U
42
43 @CTX.contextmanager
44 def tempdir():
45 d = TF.mkdtemp()
46 try: yield d
47 finally: SH.rmtree(d, ignore_errors = True)
48
49 def dirs_to_dump():
50 dirs = set()
51 for m in SYS.modules.itervalues():
52 try: f = m.__file__
53 except AttributeError: continue
54 d = OS.path.realpath(OS.path.dirname(f))
55 if d.startswith('/usr/') and not d.startswith('/usr/local/'): continue
56 dirs.add(d)
57 dirs = sorted(dirs)
58 last = '!'
59 dump = []
60 for d in dirs:
61 if d.startswith(last): continue
62 dump.append(d)
63 last = d
64 return dump
65
66 def exists_subdir(subdir):
67 return lambda dir: OS.path.isdir(OS.path.join(dir, subdir))
68
69 def filez(cmd):
70 def _(dir):
71 kid = SUB.Popen(SL.split(cmd), stdout = SUB.PIPE, cwd = dir)
72 left = ''
73 while True:
74 buf = kid.stdout.read(16384)
75 if not buf: break
76 buf = left + buf
77 i = 0
78 while True:
79 z = buf.find('\0', i)
80 if z < 0: break
81 f = buf[i:z]
82 if f.startswith('./'): f = f[2:]
83 yield f
84 i = z + 1
85 left = buf[i:]
86 if left:
87 raise U.ExpectedError, \
88 (500, "trailing junk from `%s' in `%s'" % (cmd, dir))
89 return _
90
91 DUMPERS = [
92 (exists_subdir('.git'), [filez('git ls-files -coz --exclude-standard'),
93 filez('find .git -print0')]),
94 (lambda d: True, [filez('find . ( ! -perm +004 -prune ) -o -print0')])]
95
96 def dump_dir(dir, tf, root):
97 for test, listers in DUMPERS:
98 if test(dir): break
99 else:
100 raise U.ExpectedError, (500, "no dumper for `%s'" % dir)
101 for lister in listers:
102 base = OS.path.basename(dir)
103 for file in lister(dir):
104 tf.add(OS.path.join(dir, file), OS.path.join(root, base, file),
105 recursive = False)
106
107 def source(out):
108 if SYS.version_info >= (2, 6):
109 tf = TAR.open(fileobj = out, mode = 'w|gz', format = TAR.USTAR_FORMAT)
110 else:
111 tf = TAR.open(fileobj = out, mode = 'w|gz')
112 tf.posix = True
113 root = '%s-%s' % (PACKAGE, VERSION)
114 seen = set()
115 dirmap = []
116 festout = StringIO()
117 for dir in dirs_to_dump():
118 dir = dir.rstrip('/')
119 base = OS.path.basename(dir)
120 if base not in seen:
121 name = base
122 else:
123 for i in I.count():
124 name = '%s.%d' % (base, i)
125 if name not in seen: break
126 dirmap.append((dir + '/', name))
127 festout.write('%s = %s\n' % (name, dir))
128 fest = festout.getvalue()
129 ti = TAR.TarInfo(OS.path.join(root, 'MANIFEST'))
130 ti.size = len(fest)
131 ti.mtime = T.time()
132 ti.mode = 0664
133 ti.type = TAR.REGTYPE
134 uid = OS.getuid(); ti.uid, ti.uname = uid, PW.getpwuid(uid).pw_name
135 gid = OS.getgid(); ti.gid, ti.gname = gid, GR.getgrgid(gid).gr_name
136 tf.addfile(ti, fileobj = StringIO(fest))
137 for dir, name in dirmap:
138 dump_dir(dir, tf, root)
139 tf.close()
140
141 ###----- That's all, folks --------------------------------------------------