Merge lp:~cjwatson/txpkgupload/drop-distro-files into lp:~lazr-developers/txpkgupload/trunk
- drop-distro-files
- Merge into trunk
Proposed by
Colin Watson
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 19 | ||||||||
Proposed branch: | lp:~cjwatson/txpkgupload/drop-distro-files | ||||||||
Merge into: | lp:~lazr-developers/txpkgupload/trunk | ||||||||
Diff against target: |
826 lines (+95/-462) 8 files modified
etc/txpkgupload.yaml (+5/-1) src/txpkgupload/glock.py (+0/-316) src/txpkgupload/hooks.py (+23/-106) src/txpkgupload/plugin.py (+22/-6) src/txpkgupload/tests/test_plugin.py (+23/-12) src/txpkgupload/tests/test_twistedsftp.py (+8/-6) src/txpkgupload/twistedftp.py (+11/-11) src/txpkgupload/twistedsftp.py (+3/-4) |
||||||||
To merge this branch: | bzr merge lp:~cjwatson/txpkgupload/drop-distro-files | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email:
|
Commit message
Institute a temporary directory on the same filesystem as the upload root; stop creating obsolete .distro files; remove locking, since we now move directories into place atomically.
Description of the change
Institute a temporary directory on the same filesystem as the upload root; stop creating obsolete .distro files; remove locking, since we now move directories into place atomically.
Before deploying this, we need to make sure that the tmp-incoming directory is created with appropriate permissions, and it wouldn't hurt to land an lp-production-
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Grant (wgrant) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'etc/txpkgupload.yaml' | |||
2 | --- etc/txpkgupload.yaml 2015-01-12 13:14:55 +0000 | |||
3 | +++ etc/txpkgupload.yaml 2015-01-21 13:10:45 +0000 | |||
4 | @@ -41,7 +41,11 @@ | |||
5 | 41 | # idle_timeout: 3600 | 41 | # idle_timeout: 3600 |
6 | 42 | 42 | ||
7 | 43 | ## Where on the filesystem do uploads live? | 43 | ## Where on the filesystem do uploads live? |
9 | 44 | # fsroot: "/var/tmp/txpkgupload" | 44 | # fsroot: "/var/tmp/txpkgupload/incoming" |
10 | 45 | |||
11 | 46 | ## Where do we write temporary files during uploads? This must be on the | ||
12 | 47 | ## same filesystem as fsroot. | ||
13 | 48 | # temp_dir: "/var/tmp/txpkgupload/tmp-incoming" | ||
14 | 45 | 49 | ||
15 | 46 | # If true, enable additional debug logging. | 50 | # If true, enable additional debug logging. |
16 | 47 | # debug: false | 51 | # debug: false |
17 | 48 | 52 | ||
18 | === removed file 'src/txpkgupload/glock.py' | |||
19 | --- src/txpkgupload/glock.py 2015-01-07 14:20:13 +0000 | |||
20 | +++ src/txpkgupload/glock.py 1970-01-01 00:00:00 +0000 | |||
21 | @@ -1,316 +0,0 @@ | |||
22 | 1 | #!/usr/bin/env python | ||
23 | 2 | # -*- coding: latin1 -*- | ||
24 | 3 | #---------------------------------------------------------------------------- | ||
25 | 4 | # glock.py: Global mutex | ||
26 | 5 | # | ||
27 | 6 | # See __doc__ string below. | ||
28 | 7 | # | ||
29 | 8 | # Requires: | ||
30 | 9 | # - Python 1.5.2 or newer (www.python.org) | ||
31 | 10 | # - On windows: win32 extensions installed | ||
32 | 11 | # (http://www.python.org/windows/win32all/win32all.exe) | ||
33 | 12 | # - OS: Unix, Windows. | ||
34 | 13 | # | ||
35 | 14 | # $Id: //depot/rgutils/rgutils/glock.py#5 $ | ||
36 | 15 | #---------------------------------------------------------------------------- | ||
37 | 16 | ''' | ||
38 | 17 | This module defines the class GlobalLock that implements a global | ||
39 | 18 | (inter-process) mutex on Windows and Unix, using file-locking on | ||
40 | 19 | Unix. | ||
41 | 20 | |||
42 | 21 | @see: class L{GlobalLock} for more details. | ||
43 | 22 | ''' | ||
44 | 23 | __version__ = '0.2.' + '$Revision: #5 $'[12:-2] | ||
45 | 24 | __author__ = 'Richard Gruet', 'rjgruet@yahoo.com' | ||
46 | 25 | __date__ = '$Date: 2005/06/19 $'[7:-2], '$Author: rgruet $'[9:-2] | ||
47 | 26 | __since__ = '2000-01-22' | ||
48 | 27 | __doc__ += '\n@author: %s (U{%s})\n@version: %s' % (__author__[0], | ||
49 | 28 | __author__[1], __version__) | ||
50 | 29 | __all__ = ['GlobalLock', 'GlobalLockError', 'LockAlreadyAcquired', 'NotOwner'] | ||
51 | 30 | |||
52 | 31 | # Imports: | ||
53 | 32 | import sys, string, os, errno, re | ||
54 | 33 | |||
55 | 34 | # System-dependent imports for locking implementation: | ||
56 | 35 | _windows = (sys.platform == 'win32') | ||
57 | 36 | |||
58 | 37 | if _windows: | ||
59 | 38 | try: | ||
60 | 39 | import win32event, win32api, pywintypes | ||
61 | 40 | except ImportError: | ||
62 | 41 | sys.stderr.write('The win32 extensions need to be installed!') | ||
63 | 42 | try: | ||
64 | 43 | import ctypes | ||
65 | 44 | except ImportError: | ||
66 | 45 | ctypes = None | ||
67 | 46 | else: # assume Unix | ||
68 | 47 | try: | ||
69 | 48 | import fcntl | ||
70 | 49 | except ImportError: | ||
71 | 50 | sys.stderr.write("On what kind of OS am I ? (Mac?) I should be on " | ||
72 | 51 | "Unix but can't import fcntl.\n") | ||
73 | 52 | raise | ||
74 | 53 | import threading | ||
75 | 54 | |||
76 | 55 | # Exceptions : | ||
77 | 56 | # ---------- | ||
78 | 57 | class GlobalLockError(Exception): | ||
79 | 58 | ''' Error raised by the glock module. | ||
80 | 59 | ''' | ||
81 | 60 | pass | ||
82 | 61 | |||
83 | 62 | class NotOwner(GlobalLockError): | ||
84 | 63 | ''' Attempt to release somebody else's lock. | ||
85 | 64 | ''' | ||
86 | 65 | pass | ||
87 | 66 | |||
88 | 67 | class LockAlreadyAcquired(GlobalLockError): | ||
89 | 68 | ''' Non-blocking acquire but lock already seized. | ||
90 | 69 | ''' | ||
91 | 70 | pass | ||
92 | 71 | |||
93 | 72 | |||
94 | 73 | # Constants | ||
95 | 74 | # ---------: | ||
96 | 75 | if sys.version[:3] < '2.2': | ||
97 | 76 | True, False = 1, 0 # built-in in Python 2.2+ | ||
98 | 77 | |||
99 | 78 | #---------------------------------------------------------------------------- | ||
100 | 79 | class GlobalLock: | ||
101 | 80 | #---------------------------------------------------------------------------- | ||
102 | 81 | ''' A global mutex. | ||
103 | 82 | |||
104 | 83 | B{Specification} | ||
105 | 84 | |||
106 | 85 | - The lock must act as a global mutex, ie block between different | ||
107 | 86 | candidate processus, but ALSO between different candidate | ||
108 | 87 | threads of the same process. | ||
109 | 88 | |||
110 | 89 | - It must NOT block in case of reentrant lock request issued by | ||
111 | 90 | the SAME thread. | ||
112 | 91 | - Extraneous unlocks should be ideally harmless. | ||
113 | 92 | |||
114 | 93 | B{Implementation} | ||
115 | 94 | |||
116 | 95 | In Python there is no portable global lock AFAIK. There is only a | ||
117 | 96 | LOCAL/ in-process Lock mechanism (threading.RLock), so we have to | ||
118 | 97 | implement our own solution: | ||
119 | 98 | |||
120 | 99 | - Unix: use fcntl.flock(). Recursive calls OK. Different process OK. | ||
121 | 100 | But <> threads, same process don't block so we have to use an extra | ||
122 | 101 | threading.RLock to fix that point. | ||
123 | 102 | - Windows: We use WIN32 mutex from Python Win32 extensions. Can't use | ||
124 | 103 | std module msvcrt.locking(), because global lock is OK, but | ||
125 | 104 | blocks also for 2 calls from the same thread! | ||
126 | 105 | ''' | ||
127 | 106 | RE_ERROR_MSG = re.compile ("^\[Errno ([0-9]+)\]") | ||
128 | 107 | |||
129 | 108 | def __init__(self, fpath, lockInitially=False, logger=None): | ||
130 | 109 | ''' Creates (or opens) a global lock. | ||
131 | 110 | |||
132 | 111 | @param fpath: Path of the file used as lock target. This is also | ||
133 | 112 | the global id of the lock. The file will be created | ||
134 | 113 | if non existent. | ||
135 | 114 | @param lockInitially: if True locks initially. | ||
136 | 115 | @param logger: an optional logger object. | ||
137 | 116 | ''' | ||
138 | 117 | self.logger = logger | ||
139 | 118 | self.fpath = fpath | ||
140 | 119 | if os.path.exists(fpath): | ||
141 | 120 | self.previous_lockfile_present = True | ||
142 | 121 | else: | ||
143 | 122 | self.previous_lockfile_present = False | ||
144 | 123 | if _windows: | ||
145 | 124 | self.name = string.replace(fpath, '\\', '_') | ||
146 | 125 | self.mutex = win32event.CreateMutex(None, lockInitially, self.name) | ||
147 | 126 | else: # Unix | ||
148 | 127 | self.name = fpath | ||
149 | 128 | self.flock = open(fpath, 'w') | ||
150 | 129 | self.fdlock = self.flock.fileno() | ||
151 | 130 | self.threadLock = threading.RLock() | ||
152 | 131 | if lockInitially: | ||
153 | 132 | self.acquire() | ||
154 | 133 | |||
155 | 134 | def __del__(self): | ||
156 | 135 | #print '__del__ called' ## | ||
157 | 136 | try: self.release() | ||
158 | 137 | except: pass | ||
159 | 138 | if _windows: | ||
160 | 139 | win32api.CloseHandle(self.mutex) | ||
161 | 140 | else: | ||
162 | 141 | try: self.flock.close() | ||
163 | 142 | except: pass | ||
164 | 143 | |||
165 | 144 | def __repr__(self): | ||
166 | 145 | return '<Global lock @ %s>' % self.name | ||
167 | 146 | |||
168 | 147 | def acquire(self, blocking=False): | ||
169 | 148 | """ Locks. Attemps to acquire a lock. | ||
170 | 149 | |||
171 | 150 | @param blocking: If True, suspends caller until done. Otherwise, | ||
172 | 151 | LockAlreadyAcquired is raised if the lock cannot be acquired immediately. | ||
173 | 152 | |||
174 | 153 | On windows an IOError is always raised after ~10 sec if the lock | ||
175 | 154 | can't be acquired. | ||
176 | 155 | @exception GlobalLockError: if lock can't be acquired (timeout) | ||
177 | 156 | @exception LockAlreadyAcquired: someone already has the lock and | ||
178 | 157 | the caller decided not to block. | ||
179 | 158 | """ | ||
180 | 159 | if self.logger: | ||
181 | 160 | self.logger.info('Creating lockfile: %s', self.fpath) | ||
182 | 161 | if _windows: | ||
183 | 162 | if blocking: | ||
184 | 163 | timeout = win32event.INFINITE | ||
185 | 164 | else: | ||
186 | 165 | timeout = 0 | ||
187 | 166 | r = win32event.WaitForSingleObject(self.mutex, timeout) | ||
188 | 167 | if r == win32event.WAIT_FAILED: | ||
189 | 168 | raise GlobalLockError("Can't acquire mutex: error") | ||
190 | 169 | if not blocking and r == win32event.WAIT_TIMEOUT: | ||
191 | 170 | raise LockAlreadyAcquired('Lock %s already acquired by ' | ||
192 | 171 | 'someone else' % self.name) | ||
193 | 172 | else: | ||
194 | 173 | # First, acquire the global (inter-process) lock: | ||
195 | 174 | if blocking: | ||
196 | 175 | options = fcntl.LOCK_EX | ||
197 | 176 | else: | ||
198 | 177 | options = fcntl.LOCK_EX|fcntl.LOCK_NB | ||
199 | 178 | try: | ||
200 | 179 | fcntl.flock(self.fdlock, options) | ||
201 | 180 | except IOError as message: #(errno 13: perm. denied, | ||
202 | 181 | # 36: Resource deadlock avoided) | ||
203 | 182 | if not blocking and self._errnoOf (message) == errno.EWOULDBLOCK: | ||
204 | 183 | raise LockAlreadyAcquired('Lock %s already acquired by ' | ||
205 | 184 | 'someone else' % self.name) | ||
206 | 185 | else: | ||
207 | 186 | raise GlobalLockError('Cannot acquire lock on "file" ' | ||
208 | 187 | '%s: %s\n' % (self.name, message)) | ||
209 | 188 | #print 'got file lock.' ## | ||
210 | 189 | |||
211 | 190 | # Then acquire the local (inter-thread) lock: | ||
212 | 191 | if not self.threadLock.acquire(blocking): | ||
213 | 192 | fcntl.flock(self.fdlock, fcntl.LOCK_UN) # release global lock | ||
214 | 193 | raise LockAlreadyAcquired('Lock %s already acquired by ' | ||
215 | 194 | 'someone else' % self.name) | ||
216 | 195 | if self.previous_lockfile_present and self.logger: | ||
217 | 196 | self.logger.warn("Stale lockfile detected and claimed.") | ||
218 | 197 | #print 'got thread lock.' ## | ||
219 | 198 | |||
220 | 199 | self.is_locked = True | ||
221 | 200 | |||
222 | 201 | def release(self, skip_delete=False): | ||
223 | 202 | ''' Unlocks. (caller must own the lock!) | ||
224 | 203 | |||
225 | 204 | @param skip_delete: don't try to delete the file. This can | ||
226 | 205 | be used when the original filename has changed; for | ||
227 | 206 | instance, if the lockfile is erased out-of-band, or if | ||
228 | 207 | the directory it contains has been renamed. | ||
229 | 208 | |||
230 | 209 | @return: The lock count. | ||
231 | 210 | @exception IOError: if file lock can't be released | ||
232 | 211 | @exception NotOwner: Attempt to release somebody else's lock. | ||
233 | 212 | ''' | ||
234 | 213 | if not self.is_locked: | ||
235 | 214 | return | ||
236 | 215 | if not skip_delete: | ||
237 | 216 | if self.logger: | ||
238 | 217 | self.logger.debug('Removing lock file: %s', self.fpath) | ||
239 | 218 | os.unlink(self.fpath) | ||
240 | 219 | elif self.logger: | ||
241 | 220 | # At certain times the lockfile will have been removed or | ||
242 | 221 | # moved away before we call release(); log a message because | ||
243 | 222 | # this is unusual and could be an error. | ||
244 | 223 | self.logger.debug('Oops, my lock file disappeared: %s', self.fpath) | ||
245 | 224 | if _windows: | ||
246 | 225 | if ctypes: | ||
247 | 226 | result = ctypes.windll.kernel32.ReleaseMutex(self.mutex.handle) | ||
248 | 227 | if not result: | ||
249 | 228 | raise NotOwner("Attempt to release somebody else's lock") | ||
250 | 229 | else: | ||
251 | 230 | try: | ||
252 | 231 | win32event.ReleaseMutex(self.mutex) | ||
253 | 232 | #print "released mutex" | ||
254 | 233 | except pywintypes.error as e: | ||
255 | 234 | errCode, fctName, errMsg = e.args | ||
256 | 235 | if errCode == 288: | ||
257 | 236 | raise NotOwner("Attempt to release somebody else's lock") | ||
258 | 237 | else: | ||
259 | 238 | raise GlobalLockError('%s: err#%d: %s' % (fctName, errCode, | ||
260 | 239 | errMsg)) | ||
261 | 240 | else: | ||
262 | 241 | # First release the local (inter-thread) lock: | ||
263 | 242 | try: | ||
264 | 243 | self.threadLock.release() | ||
265 | 244 | except AssertionError: | ||
266 | 245 | raise NotOwner("Attempt to release somebody else's lock") | ||
267 | 246 | |||
268 | 247 | # Then release the global (inter-process) lock: | ||
269 | 248 | try: | ||
270 | 249 | fcntl.flock(self.fdlock, fcntl.LOCK_UN) | ||
271 | 250 | except IOError: # (errno 13: permission denied) | ||
272 | 251 | raise GlobalLockError('Unlock of file "%s" failed\n' % | ||
273 | 252 | self.name) | ||
274 | 253 | self.is_locked = False | ||
275 | 254 | |||
276 | 255 | def _errnoOf (self, message): | ||
277 | 256 | match = self.RE_ERROR_MSG.search(str(message)) | ||
278 | 257 | if match: | ||
279 | 258 | return int(match.group(1)) | ||
280 | 259 | else: | ||
281 | 260 | raise Exception ('Malformed error message "%s"' % message) | ||
282 | 261 | |||
283 | 262 | #---------------------------------------------------------------------------- | ||
284 | 263 | def test(): | ||
285 | 264 | #---------------------------------------------------------------------------- | ||
286 | 265 | ##TODO: a more serious test with distinct processes ! | ||
287 | 266 | |||
288 | 267 | print 'Testing glock.py...' | ||
289 | 268 | |||
290 | 269 | # unfortunately can't test inter-process lock here! | ||
291 | 270 | lockName = 'myFirstLock' | ||
292 | 271 | l = GlobalLock(lockName) | ||
293 | 272 | if not _windows: | ||
294 | 273 | assert os.path.exists(lockName) | ||
295 | 274 | l.acquire() | ||
296 | 275 | l.acquire() # reentrant lock, must not block | ||
297 | 276 | l.release() | ||
298 | 277 | l.release() | ||
299 | 278 | |||
300 | 279 | try: l.release() | ||
301 | 280 | except NotOwner: pass | ||
302 | 281 | else: raise Exception('should have raised a NotOwner exception') | ||
303 | 282 | |||
304 | 283 | # Check that <> threads of same process do block: | ||
305 | 284 | import threading, time | ||
306 | 285 | thread = threading.Thread(target=threadMain, args=(l,)) | ||
307 | 286 | print 'main: locking...', | ||
308 | 287 | l.acquire() | ||
309 | 288 | print ' done.' | ||
310 | 289 | thread.start() | ||
311 | 290 | time.sleep(3) | ||
312 | 291 | print '\nmain: unlocking...', | ||
313 | 292 | l.release() | ||
314 | 293 | print ' done.' | ||
315 | 294 | time.sleep(0.1) | ||
316 | 295 | |||
317 | 296 | print '=> Test of glock.py passed.' | ||
318 | 297 | return l | ||
319 | 298 | |||
320 | 299 | def threadMain(lock): | ||
321 | 300 | print 'thread started(%s).' % lock | ||
322 | 301 | try: lock.acquire(blocking=False) | ||
323 | 302 | except LockAlreadyAcquired: pass | ||
324 | 303 | else: raise Exception('should have raised LockAlreadyAcquired') | ||
325 | 304 | print 'thread: locking (should stay blocked for ~ 3 sec)...', | ||
326 | 305 | lock.acquire() | ||
327 | 306 | print 'thread: locking done.' | ||
328 | 307 | print 'thread: unlocking...', | ||
329 | 308 | lock.release() | ||
330 | 309 | print ' done.' | ||
331 | 310 | print 'thread ended.' | ||
332 | 311 | |||
333 | 312 | #---------------------------------------------------------------------------- | ||
334 | 313 | # M A I N | ||
335 | 314 | #---------------------------------------------------------------------------- | ||
336 | 315 | if __name__ == "__main__": | ||
337 | 316 | l = test() | ||
338 | 317 | 0 | ||
339 | === modified file 'src/txpkgupload/hooks.py' | |||
340 | --- src/txpkgupload/hooks.py 2015-01-09 15:55:08 +0000 | |||
341 | +++ src/txpkgupload/hooks.py 2015-01-21 13:10:45 +0000 | |||
342 | @@ -10,14 +10,10 @@ | |||
343 | 10 | 10 | ||
344 | 11 | 11 | ||
345 | 12 | import os | 12 | import os |
346 | 13 | import shutil | ||
347 | 14 | import stat | ||
348 | 15 | import time | 13 | import time |
349 | 16 | 14 | ||
350 | 17 | from twisted.python import log | 15 | from twisted.python import log |
351 | 18 | 16 | ||
352 | 19 | from txpkgupload.glock import GlobalLock | ||
353 | 20 | |||
354 | 21 | 17 | ||
355 | 22 | class InterfaceFailure(Exception): | 18 | class InterfaceFailure(Exception): |
356 | 23 | pass | 19 | pass |
357 | @@ -29,11 +25,8 @@ | |||
358 | 29 | LOG_MAGIC = "Post-processing finished" | 25 | LOG_MAGIC = "Post-processing finished" |
359 | 30 | _targetcount = 0 | 26 | _targetcount = 0 |
360 | 31 | 27 | ||
363 | 32 | def __init__(self, targetpath, allow_user, cmd=None, | 28 | def __init__(self, targetpath, targetstart=0, perms=None, prefix=''): |
362 | 33 | targetstart=0, perms=None, prefix=''): | ||
364 | 34 | self.targetpath = targetpath | 29 | self.targetpath = targetpath |
365 | 35 | self.cmd = cmd | ||
366 | 36 | self.allow_user = allow_user | ||
367 | 37 | self.perms = perms | 30 | self.perms = perms |
368 | 38 | self.prefix = prefix | 31 | self.prefix = prefix |
369 | 39 | 32 | ||
370 | @@ -53,9 +46,7 @@ | |||
371 | 53 | log.msg("Session from %s:%s" % (host, port), debug=True) | 46 | log.msg("Session from %s:%s" % (host, port), debug=True) |
372 | 54 | 47 | ||
373 | 55 | def client_done_hook(self, fsroot, host, port): | 48 | def client_done_hook(self, fsroot, host, port): |
377 | 56 | """A client has completed. If it authenticated then it stands a chance | 49 | """A client has completed.""" |
375 | 57 | of having uploaded a file to the set. If not; then it is simply an | ||
376 | 58 | aborted transaction and we remove the fsroot.""" | ||
378 | 59 | 50 | ||
379 | 60 | if fsroot not in self.clients: | 51 | if fsroot not in self.clients: |
380 | 61 | raise InterfaceFailure("Unable to find fsroot in client set") | 52 | raise InterfaceFailure("Unable to find fsroot in client set") |
381 | @@ -63,103 +54,29 @@ | |||
382 | 63 | log.msg("Processing session complete in %s" % fsroot, debug=True) | 54 | log.msg("Processing session complete in %s" % fsroot, debug=True) |
383 | 64 | 55 | ||
384 | 65 | client = self.clients[fsroot] | 56 | client = self.clients[fsroot] |
455 | 66 | if "distro" not in client: | 57 | |
456 | 67 | # Login username defines the distribution context of the upload. | 58 | timestamp = time.strftime("%Y%m%d-%H%M%S") |
457 | 68 | # So abort unauthenticated sessions by removing its contents | 59 | path = "upload%s-%s-%06d" % ( |
458 | 69 | shutil.rmtree(fsroot) | 60 | self.prefix, timestamp, self.targetcount) |
459 | 70 | return | 61 | target_fsroot = os.path.join(self.targetpath, path) |
460 | 71 | 62 | ||
461 | 72 | # Protect from race condition between creating the directory | 63 | # Move the session directory to the target directory. |
462 | 73 | # and creating the distro file, and also in cases where the | 64 | if os.path.exists(target_fsroot): |
463 | 74 | # temporary directory and the upload directory are not in the | 65 | log.msg("Targeted upload already present: %s" % path) |
464 | 75 | # same filesystem (non-atomic "rename"). | 66 | log.msg("System clock skewed?") |
465 | 76 | lockfile_path = os.path.join(self.targetpath, ".lock") | 67 | else: |
466 | 77 | self.lock = GlobalLock(lockfile_path) | 68 | try: |
467 | 78 | 69 | os.rename(fsroot, target_fsroot) | |
468 | 79 | # XXX cprov 20071024 bug=156795: We try to acquire the lock as soon | 70 | except (OSError, IOError): |
469 | 80 | # as possible after creating the lockfile but are still open to | 71 | if not os.path.exists(target_fsroot): |
470 | 81 | # a race. | 72 | raise |
471 | 82 | self.lock.acquire(blocking=True) | 73 | |
472 | 83 | mode = stat.S_IMODE(os.stat(lockfile_path).st_mode) | 74 | # XXX cprov 20071024: We should replace os.system call by os.chmod |
473 | 84 | 75 | # and fix the default permission value accordingly in txpkgupload | |
474 | 85 | # XXX cprov 20081024 bug=185731: The lockfile permission can only be | 76 | if self.perms is not None: |
475 | 86 | # changed by its owner. Since we can't predict which process will | 77 | os.system("chmod %s -R %s" % (self.perms, target_fsroot)) |
406 | 87 | # create it in production systems we simply ignore errors when trying | ||
407 | 88 | # to grant the right permission. At least, one of the process will | ||
408 | 89 | # be able to do so. | ||
409 | 90 | try: | ||
410 | 91 | os.chmod(lockfile_path, mode | stat.S_IWGRP) | ||
411 | 92 | except OSError: | ||
412 | 93 | pass | ||
413 | 94 | |||
414 | 95 | try: | ||
415 | 96 | timestamp = time.strftime("%Y%m%d-%H%M%S") | ||
416 | 97 | path = "upload%s-%s-%06d" % ( | ||
417 | 98 | self.prefix, timestamp, self.targetcount) | ||
418 | 99 | target_fsroot = os.path.join(self.targetpath, path) | ||
419 | 100 | |||
420 | 101 | # Create file to store the distro used. | ||
421 | 102 | log.msg( | ||
422 | 103 | "Upload was targetted at %s" % client["distro"], debug=True) | ||
423 | 104 | distro_filename = target_fsroot + ".distro" | ||
424 | 105 | distro_file = open(distro_filename, "w") | ||
425 | 106 | distro_file.write(client["distro"]) | ||
426 | 107 | distro_file.close() | ||
427 | 108 | |||
428 | 109 | # Move the session directory to the target directory. | ||
429 | 110 | if os.path.exists(target_fsroot): | ||
430 | 111 | log.msg("Targeted upload already present: %s" % path) | ||
431 | 112 | log.msg("System clock skewed ?") | ||
432 | 113 | else: | ||
433 | 114 | try: | ||
434 | 115 | shutil.move(fsroot, target_fsroot) | ||
435 | 116 | except (OSError, IOError): | ||
436 | 117 | if not os.path.exists(target_fsroot): | ||
437 | 118 | raise | ||
438 | 119 | |||
439 | 120 | # XXX cprov 20071024: We should replace os.system call by os.chmod | ||
440 | 121 | # and fix the default permission value accordingly in txpkgupload | ||
441 | 122 | if self.perms is not None: | ||
442 | 123 | os.system("chmod %s -R %s" % (self.perms, target_fsroot)) | ||
443 | 124 | |||
444 | 125 | # Invoke processing script, if provided. | ||
445 | 126 | if self.cmd: | ||
446 | 127 | cmd = self.cmd | ||
447 | 128 | cmd = cmd.replace("@fsroot@", target_fsroot) | ||
448 | 129 | cmd = cmd.replace("@distro@", client["distro"]) | ||
449 | 130 | log.msg("Running upload handler: %s" % cmd, debug=True) | ||
450 | 131 | os.system(cmd) | ||
451 | 132 | finally: | ||
452 | 133 | # We never delete the lockfile, this way the inode will be | ||
453 | 134 | # constant while the machine is up. See comment on 'acquire' | ||
454 | 135 | self.lock.release(skip_delete=True) | ||
476 | 136 | 78 | ||
477 | 137 | self.clients.pop(fsroot) | 79 | self.clients.pop(fsroot) |
478 | 138 | # This is mainly done so that tests know when the | 80 | # This is mainly done so that tests know when the |
479 | 139 | # post-processing hook has finished. | 81 | # post-processing hook has finished. |
480 | 140 | log.msg(self.LOG_MAGIC) | 82 | log.msg(self.LOG_MAGIC) |
481 | 141 | |||
482 | 142 | def auth_verify_hook(self, fsroot, user, password): | ||
483 | 143 | """Verify that the username matches a distribution we care about. | ||
484 | 144 | |||
485 | 145 | The password is irrelevant to auth, as is the fsroot""" | ||
486 | 146 | if fsroot not in self.clients: | ||
487 | 147 | raise InterfaceFailure("Unable to find fsroot in client set") | ||
488 | 148 | |||
489 | 149 | # local authentication | ||
490 | 150 | self.clients[fsroot]["distro"] = self.allow_user | ||
491 | 151 | return True | ||
492 | 152 | |||
493 | 153 | # When we get on with the txpkgupload path stuff, the below may be | ||
494 | 154 | # useful and is thus left in rather than being removed. | ||
495 | 155 | |||
496 | 156 | #try: | ||
497 | 157 | # d = Distribution.byName(user) | ||
498 | 158 | # if d: | ||
499 | 159 | # log.msg("Accepting login for %s" % user, debug=True) | ||
500 | 160 | # self.clients[fsroot]["distro"] = user | ||
501 | 161 | # return True | ||
502 | 162 | #except object as e: | ||
503 | 163 | # print e | ||
504 | 164 | #return False | ||
505 | 165 | |||
506 | 166 | 83 | ||
507 | === modified file 'src/txpkgupload/plugin.py' | |||
508 | --- src/txpkgupload/plugin.py 2015-01-12 18:50:14 +0000 | |||
509 | +++ src/txpkgupload/plugin.py 2015-01-21 13:10:45 +0000 | |||
510 | @@ -7,6 +7,7 @@ | |||
511 | 7 | ] | 7 | ] |
512 | 8 | 8 | ||
513 | 9 | from functools import partial | 9 | from functools import partial |
514 | 10 | import os | ||
515 | 10 | 11 | ||
516 | 11 | from formencode import Schema | 12 | from formencode import Schema |
517 | 12 | from formencode.api import set_stdtranslation | 13 | from formencode.api import set_stdtranslation |
518 | @@ -119,6 +120,10 @@ | |||
519 | 119 | # Where on the filesystem do uploads live? | 120 | # Where on the filesystem do uploads live? |
520 | 120 | fsroot = String(if_missing=None) | 121 | fsroot = String(if_missing=None) |
521 | 121 | 122 | ||
522 | 123 | # Where do we write temporary files during uploads? This must be on the | ||
523 | 124 | # same filesystem as fsroot. | ||
524 | 125 | temp_dir = String(if_missing=None) | ||
525 | 126 | |||
526 | 122 | @classmethod | 127 | @classmethod |
527 | 123 | def parse(cls, stream): | 128 | def parse(cls, stream): |
528 | 124 | """Load a YAML configuration from `stream` and validate.""" | 129 | """Load a YAML configuration from `stream` and validate.""" |
529 | @@ -143,19 +148,22 @@ | |||
530 | 143 | """An SSH avatar specific to txpkgupload. | 148 | """An SSH avatar specific to txpkgupload. |
531 | 144 | 149 | ||
532 | 145 | :ivar fs_root: The file system root for this session. | 150 | :ivar fs_root: The file system root for this session. |
533 | 151 | :ivar temp_dir: The temporary directory for this session. | ||
534 | 146 | """ | 152 | """ |
535 | 147 | 153 | ||
537 | 148 | def __init__(self, user_dict, fs_root): | 154 | def __init__(self, user_dict, fs_root, temp_dir): |
538 | 149 | LaunchpadAvatar.__init__(self, user_dict) | 155 | LaunchpadAvatar.__init__(self, user_dict) |
539 | 150 | self.fs_root = fs_root | 156 | self.fs_root = fs_root |
540 | 157 | self.temp_dir = temp_dir | ||
541 | 151 | 158 | ||
542 | 152 | 159 | ||
543 | 153 | class Realm: | 160 | class Realm: |
544 | 154 | implements(IRealm) | 161 | implements(IRealm) |
545 | 155 | 162 | ||
547 | 156 | def __init__(self, authentication_proxy, fs_root): | 163 | def __init__(self, authentication_proxy, fs_root, temp_dir): |
548 | 157 | self.authentication_proxy = authentication_proxy | 164 | self.authentication_proxy = authentication_proxy |
549 | 158 | self.fs_root = fs_root | 165 | self.fs_root = fs_root |
550 | 166 | self.temp_dir = temp_dir | ||
551 | 159 | 167 | ||
552 | 160 | def requestAvatar(self, avatar_id, mind, *interfaces): | 168 | def requestAvatar(self, avatar_id, mind, *interfaces): |
553 | 161 | # Fetch the user's details from the authserver | 169 | # Fetch the user's details from the authserver |
554 | @@ -164,20 +172,20 @@ | |||
555 | 164 | 172 | ||
556 | 165 | # Once all those details are retrieved, we can construct the avatar. | 173 | # Once all those details are retrieved, we can construct the avatar. |
557 | 166 | def got_user_dict(user_dict): | 174 | def got_user_dict(user_dict): |
559 | 167 | avatar = PkgUploadAvatar(user_dict, self.fs_root) | 175 | avatar = PkgUploadAvatar(user_dict, self.fs_root, self.temp_dir) |
560 | 168 | return interfaces[0], avatar, avatar.logout | 176 | return interfaces[0], avatar, avatar.logout |
561 | 169 | 177 | ||
562 | 170 | return deferred.addCallback(got_user_dict) | 178 | return deferred.addCallback(got_user_dict) |
563 | 171 | 179 | ||
564 | 172 | 180 | ||
566 | 173 | def make_portal(authentication_endpoint, fs_root): | 181 | def make_portal(authentication_endpoint, fs_root, temp_dir): |
567 | 174 | """Create and return a `Portal` for the SSH service. | 182 | """Create and return a `Portal` for the SSH service. |
568 | 175 | 183 | ||
569 | 176 | This portal accepts SSH credentials and returns our customized SSH | 184 | This portal accepts SSH credentials and returns our customized SSH |
570 | 177 | avatars (see `LaunchpadAvatar`). | 185 | avatars (see `LaunchpadAvatar`). |
571 | 178 | """ | 186 | """ |
572 | 179 | authentication_proxy = Proxy(authentication_endpoint) | 187 | authentication_proxy = Proxy(authentication_endpoint) |
574 | 180 | portal = Portal(Realm(authentication_proxy, fs_root)) | 188 | portal = Portal(Realm(authentication_proxy, fs_root, temp_dir)) |
575 | 181 | portal.registerChecker( | 189 | portal.registerChecker( |
576 | 182 | PublicKeyFromLaunchpadChecker(authentication_proxy)) | 190 | PublicKeyFromLaunchpadChecker(authentication_proxy)) |
577 | 183 | return portal | 191 | return portal |
578 | @@ -212,18 +220,26 @@ | |||
579 | 212 | oops_dir, oops_reporter, server_argv=server_argv) | 220 | oops_dir, oops_reporter, server_argv=server_argv) |
580 | 213 | 221 | ||
581 | 214 | root = get_txpkgupload_root(config["fsroot"]) | 222 | root = get_txpkgupload_root(config["fsroot"]) |
582 | 223 | temp_dir = config["temp_dir"] | ||
583 | 224 | if temp_dir is None: | ||
584 | 225 | temp_dir = os.path.abspath(os.path.join( | ||
585 | 226 | root, os.pardir, "tmp-incoming")) | ||
586 | 227 | if not os.path.exists(temp_dir): | ||
587 | 228 | os.makedirs(temp_dir, 0775) | ||
588 | 215 | 229 | ||
589 | 216 | ftp_config = config["ftp"] | 230 | ftp_config = config["ftp"] |
590 | 217 | ftp_service = FTPServiceFactory.makeFTPService( | 231 | ftp_service = FTPServiceFactory.makeFTPService( |
591 | 218 | port=ftp_config["port"], | 232 | port=ftp_config["port"], |
592 | 219 | root=root, | 233 | root=root, |
593 | 234 | temp_dir=temp_dir, | ||
594 | 220 | idle_timeout=config["idle_timeout"]) | 235 | idle_timeout=config["idle_timeout"]) |
595 | 221 | ftp_service.name = "ftp" | 236 | ftp_service.name = "ftp" |
596 | 222 | ftp_service.setServiceParent(services) | 237 | ftp_service.setServiceParent(services) |
597 | 223 | 238 | ||
598 | 224 | sftp_config = config["sftp"] | 239 | sftp_config = config["sftp"] |
599 | 225 | sftp_service = SSHService( | 240 | sftp_service = SSHService( |
601 | 226 | portal=make_portal(sftp_config["authentication_endpoint"], root), | 241 | portal=make_portal( |
602 | 242 | sftp_config["authentication_endpoint"], root, temp_dir), | ||
603 | 227 | private_key_path=sftp_config["host_key_private"], | 243 | private_key_path=sftp_config["host_key_private"], |
604 | 228 | public_key_path=sftp_config["host_key_public"], | 244 | public_key_path=sftp_config["host_key_public"], |
605 | 229 | main_log='txpkgupload', | 245 | main_log='txpkgupload', |
606 | 230 | 246 | ||
607 | === modified file 'src/txpkgupload/tests/test_plugin.py' | |||
608 | --- src/txpkgupload/tests/test_plugin.py 2015-01-12 13:14:55 +0000 | |||
609 | +++ src/txpkgupload/tests/test_plugin.py 2015-01-21 13:10:45 +0000 | |||
610 | @@ -115,6 +115,7 @@ | |||
611 | 115 | "host_key_public": None, | 115 | "host_key_public": None, |
612 | 116 | "port": "tcp:5022", | 116 | "port": "tcp:5022", |
613 | 117 | }, | 117 | }, |
614 | 118 | "temp_dir": None, | ||
615 | 118 | } | 119 | } |
616 | 119 | observed = Config.to_python({}) | 120 | observed = Config.to_python({}) |
617 | 120 | self.assertEqual(expected, observed) | 121 | self.assertEqual(expected, observed) |
618 | @@ -262,6 +263,8 @@ | |||
619 | 262 | 263 | ||
620 | 263 | def __init__(self, root_dir): | 264 | def __init__(self, root_dir): |
621 | 264 | self.root_dir = root_dir | 265 | self.root_dir = root_dir |
622 | 266 | self.fsroot = os.path.join(self.root_dir, "incoming") | ||
623 | 267 | self.temp_dir = os.path.join(self.root_dir, "tmp-incoming") | ||
624 | 265 | top = os.path.join( | 268 | top = os.path.join( |
625 | 266 | os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) | 269 | os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) |
626 | 267 | with open(os.path.join(top, "etc", "txpkgupload.yaml")) as stream: | 270 | with open(os.path.join(top, "etc", "txpkgupload.yaml")) as stream: |
627 | @@ -270,8 +273,11 @@ | |||
628 | 270 | 273 | ||
629 | 271 | def setUp(self): | 274 | def setUp(self): |
630 | 272 | super(FTPServer, self).setUp() | 275 | super(FTPServer, self).setUp() |
633 | 273 | self.pkgupload = self.useFixture(PkgUploadFixture( | 276 | os.mkdir(self.fsroot) |
634 | 274 | "fsroot: %s" % self.root_dir)) | 277 | os.mkdir(self.temp_dir) |
635 | 278 | self.pkgupload = self.useFixture(PkgUploadFixture(dedent("""\ | ||
636 | 279 | fsroot: %s | ||
637 | 280 | temp_dir: %s""") % (self.fsroot, self.temp_dir))) | ||
638 | 275 | 281 | ||
639 | 276 | def getAnonClient(self): | 282 | def getAnonClient(self): |
640 | 277 | creator = ClientCreator( | 283 | creator = ClientCreator( |
641 | @@ -368,6 +374,8 @@ | |||
642 | 368 | 374 | ||
643 | 369 | def __init__(self, root_dir): | 375 | def __init__(self, root_dir): |
644 | 370 | self.root_dir = root_dir | 376 | self.root_dir = root_dir |
645 | 377 | self.fsroot = os.path.join(self.root_dir, "incoming") | ||
646 | 378 | self.temp_dir = os.path.join(self.root_dir, "tmp-incoming") | ||
647 | 371 | #self._factory = factory | 379 | #self._factory = factory |
648 | 372 | top = os.path.join( | 380 | top = os.path.join( |
649 | 373 | os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) | 381 | os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) |
650 | @@ -401,10 +409,14 @@ | |||
651 | 401 | self.authserver_url = b"http://localhost:%d/" % self.authserver_port | 409 | self.authserver_url = b"http://localhost:%d/" % self.authserver_port |
652 | 402 | self.addCleanup(self.authserver_listener.stopListening) | 410 | self.addCleanup(self.authserver_listener.stopListening) |
653 | 403 | self.setUpUser('joe') | 411 | self.setUpUser('joe') |
654 | 412 | os.mkdir(self.fsroot) | ||
655 | 413 | os.mkdir(self.temp_dir) | ||
656 | 404 | self.pkgupload = self.useFixture(PkgUploadFixture(dedent("""\ | 414 | self.pkgupload = self.useFixture(PkgUploadFixture(dedent("""\ |
657 | 405 | sftp: | 415 | sftp: |
658 | 406 | authentication_endpoint: %s | 416 | authentication_endpoint: %s |
660 | 407 | fsroot: %s""") % (self.authserver_url, self.root_dir))) | 417 | fsroot: %s |
661 | 418 | temp_dir: %s""") % | ||
662 | 419 | (self.authserver_url, self.fsroot, self.temp_dir))) | ||
663 | 408 | 420 | ||
664 | 409 | @defer.inlineCallbacks | 421 | @defer.inlineCallbacks |
665 | 410 | def getClient(self): | 422 | def getClient(self): |
666 | @@ -460,8 +472,8 @@ | |||
667 | 460 | def setUp(self): | 472 | def setUp(self): |
668 | 461 | """Set up txpkgupload in a temp dir.""" | 473 | """Set up txpkgupload in a temp dir.""" |
669 | 462 | super(TestPkgUploadServiceMakerMixin, self).setUp() | 474 | super(TestPkgUploadServiceMakerMixin, self).setUp() |
672 | 463 | self.root_dir = self.useFixture(TempDir()).path | 475 | root_dir = self.useFixture(TempDir()).path |
673 | 464 | self.server = self.server_factory(self.root_dir) | 476 | self.server = self.server_factory(root_dir) |
674 | 465 | self.useFixture(self.server) | 477 | self.useFixture(self.server) |
675 | 466 | 478 | ||
676 | 467 | def test_init(self): | 479 | def test_init(self): |
677 | @@ -484,9 +496,9 @@ | |||
678 | 484 | 496 | ||
679 | 485 | Only works for a single upload (txpkgupload transaction). | 497 | Only works for a single upload (txpkgupload transaction). |
680 | 486 | """ | 498 | """ |
684 | 487 | contents = sorted(os.listdir(self.root_dir)) | 499 | contents = sorted(os.listdir(self.server.fsroot)) |
685 | 488 | upload_dir = contents[1] | 500 | upload_dir = contents[0] |
686 | 489 | return os.path.join(self.root_dir, upload_dir, path) | 501 | return os.path.join(self.server.fsroot, upload_dir, path) |
687 | 490 | 502 | ||
688 | 491 | @defer.inlineCallbacks | 503 | @defer.inlineCallbacks |
689 | 492 | def test_mkdir(self): | 504 | def test_mkdir(self): |
690 | @@ -611,16 +623,15 @@ | |||
691 | 611 | yield self.server.waitForClose(4) | 623 | yield self.server.waitForClose(4) |
692 | 612 | 624 | ||
693 | 613 | # Build a list of directories representing the 4 sessions. | 625 | # Build a list of directories representing the 4 sessions. |
697 | 614 | upload_dirs = [leaf for leaf in sorted(os.listdir(self.root_dir)) | 626 | upload_dirs = [leaf for leaf in sorted(os.listdir(self.server.fsroot)) |
698 | 615 | if not leaf.startswith(".") and | 627 | if not leaf.startswith(".")] |
696 | 616 | not leaf.endswith(".distro")] | ||
699 | 617 | self.assertEqual(len(upload_dirs), 4) | 628 | self.assertEqual(len(upload_dirs), 4) |
700 | 618 | 629 | ||
701 | 619 | # Check the contents of files on each session. | 630 | # Check the contents of files on each session. |
702 | 620 | expected_contents = ['ONE', 'TWO', 'THREE', 'FOUR'] | 631 | expected_contents = ['ONE', 'TWO', 'THREE', 'FOUR'] |
703 | 621 | for index in range(4): | 632 | for index in range(4): |
704 | 622 | content = open(os.path.join( | 633 | content = open(os.path.join( |
706 | 623 | self.root_dir, upload_dirs[index], "test")).read() | 634 | self.server.fsroot, upload_dirs[index], "test")).read() |
707 | 624 | self.assertEqual(content, expected_contents[index]) | 635 | self.assertEqual(content, expected_contents[index]) |
708 | 625 | 636 | ||
709 | 626 | 637 | ||
710 | 627 | 638 | ||
711 | === modified file 'src/txpkgupload/tests/test_twistedsftp.py' | |||
712 | --- src/txpkgupload/tests/test_twistedsftp.py 2015-01-21 12:20:52 +0000 | |||
713 | +++ src/txpkgupload/tests/test_twistedsftp.py 2015-01-21 13:10:45 +0000 | |||
714 | @@ -16,18 +16,20 @@ | |||
715 | 16 | 16 | ||
716 | 17 | class MockAvatar: | 17 | class MockAvatar: |
717 | 18 | 18 | ||
719 | 19 | def __init__(self, fs_root): | 19 | def __init__(self, fs_root, temp_dir): |
720 | 20 | self.fs_root = fs_root | 20 | self.fs_root = fs_root |
721 | 21 | self.temp_dir = temp_dir | ||
722 | 21 | 22 | ||
723 | 22 | 23 | ||
724 | 23 | class TestSFTPServer(testtools.TestCase): | 24 | class TestSFTPServer(testtools.TestCase): |
725 | 24 | 25 | ||
726 | 25 | def setUp(self): | 26 | def setUp(self): |
732 | 26 | tempdir = fixtures.TempDir() | 27 | temp_dir = self.useFixture(fixtures.TempDir()).path |
733 | 27 | self.useFixture(tempdir) | 28 | fs_root = os.path.join(temp_dir, "incoming") |
734 | 28 | self.useFixture(fixtures.MonkeyPatch("tempfile.tempdir", tempdir.path)) | 29 | temp_dir = os.path.join(temp_dir, "tmp-incoming") |
735 | 29 | self.fs_root = self.useFixture(fixtures.TempDir()).path | 30 | os.mkdir(fs_root) |
736 | 30 | self.sftp_server = SFTPServer(MockAvatar(self.fs_root)) | 31 | os.mkdir(temp_dir) |
737 | 32 | self.sftp_server = SFTPServer(MockAvatar(fs_root, temp_dir)) | ||
738 | 31 | super(TestSFTPServer, self).setUp() | 33 | super(TestSFTPServer, self).setUp() |
739 | 32 | 34 | ||
740 | 33 | def assertPermissions(self, expected, file_name): | 35 | def assertPermissions(self, expected, file_name): |
741 | 34 | 36 | ||
742 | === modified file 'src/txpkgupload/twistedftp.py' | |||
743 | --- src/txpkgupload/twistedftp.py 2015-01-09 15:55:08 +0000 | |||
744 | +++ src/txpkgupload/twistedftp.py 2015-01-21 13:10:45 +0000 | |||
745 | @@ -52,15 +52,14 @@ | |||
746 | 52 | Roughly equivalent to the SFTPServer in the sftp side of things. | 52 | Roughly equivalent to the SFTPServer in the sftp side of things. |
747 | 53 | """ | 53 | """ |
748 | 54 | 54 | ||
750 | 55 | def __init__(self, fsroot): | 55 | def __init__(self, fsroot, temp_dir): |
751 | 56 | self._fs_root = fsroot | 56 | self._fs_root = fsroot |
753 | 57 | self.uploadfilesystem = UploadFileSystem(tempfile.mkdtemp()) | 57 | self.uploadfilesystem = UploadFileSystem( |
754 | 58 | tempfile.mkdtemp(dir=temp_dir)) | ||
755 | 58 | self._current_upload = self.uploadfilesystem.rootpath | 59 | self._current_upload = self.uploadfilesystem.rootpath |
756 | 59 | os.chmod(self._current_upload, 0770) | 60 | os.chmod(self._current_upload, 0770) |
759 | 60 | self.hook = Hooks( | 61 | self.hook = Hooks(self._fs_root, perms='g+rws', prefix='-ftp') |
758 | 61 | self._fs_root, "ubuntu", perms='g+rws', prefix='-ftp') | ||
760 | 62 | self.hook.new_client_hook(self._current_upload, 0, 0) | 62 | self.hook.new_client_hook(self._current_upload, 0, 0) |
761 | 63 | self.hook.auth_verify_hook(self._current_upload, None, None) | ||
762 | 64 | super(AnonymousShell, self).__init__( | 63 | super(AnonymousShell, self).__init__( |
763 | 65 | filepath.FilePath(self._current_upload)) | 64 | filepath.FilePath(self._current_upload)) |
764 | 66 | 65 | ||
765 | @@ -113,8 +112,9 @@ | |||
766 | 113 | """FTP Realm that lets anyone in.""" | 112 | """FTP Realm that lets anyone in.""" |
767 | 114 | implements(IRealm) | 113 | implements(IRealm) |
768 | 115 | 114 | ||
770 | 116 | def __init__(self, root): | 115 | def __init__(self, root, temp_dir): |
771 | 117 | self.root = root | 116 | self.root = root |
772 | 117 | self.temp_dir = temp_dir | ||
773 | 118 | 118 | ||
774 | 119 | def requestAvatar(self, avatarId, mind, *interfaces): | 119 | def requestAvatar(self, avatarId, mind, *interfaces): |
775 | 120 | """Return a txpkgupload avatar - that is, an "authorisation". | 120 | """Return a txpkgupload avatar - that is, an "authorisation". |
776 | @@ -124,7 +124,7 @@ | |||
777 | 124 | """ | 124 | """ |
778 | 125 | for iface in interfaces: | 125 | for iface in interfaces: |
779 | 126 | if iface is ftp.IFTPShell: | 126 | if iface is ftp.IFTPShell: |
781 | 127 | avatar = AnonymousShell(self.root) | 127 | avatar = AnonymousShell(self.root, self.temp_dir) |
782 | 128 | return ftp.IFTPShell, avatar, getattr( | 128 | return ftp.IFTPShell, avatar, getattr( |
783 | 129 | avatar, 'logout', lambda: None) | 129 | avatar, 'logout', lambda: None) |
784 | 130 | raise NotImplementedError( | 130 | raise NotImplementedError( |
785 | @@ -134,8 +134,8 @@ | |||
786 | 134 | class FTPServiceFactory(service.Service): | 134 | class FTPServiceFactory(service.Service): |
787 | 135 | """A factory that makes an `FTPService`""" | 135 | """A factory that makes an `FTPService`""" |
788 | 136 | 136 | ||
791 | 137 | def __init__(self, port, root, idle_timeout): | 137 | def __init__(self, port, root, temp_dir, idle_timeout): |
792 | 138 | realm = FTPRealm(root) | 138 | realm = FTPRealm(root, temp_dir) |
793 | 139 | portal = Portal(realm) | 139 | portal = Portal(realm) |
794 | 140 | portal.registerChecker(AccessCheck()) | 140 | portal.registerChecker(AccessCheck()) |
795 | 141 | factory = ftp.FTPFactory(portal) | 141 | factory = ftp.FTPFactory(portal) |
796 | @@ -149,7 +149,7 @@ | |||
797 | 149 | self.portno = port | 149 | self.portno = port |
798 | 150 | 150 | ||
799 | 151 | @staticmethod | 151 | @staticmethod |
801 | 152 | def makeFTPService(port, root, idle_timeout): | 152 | def makeFTPService(port, root, temp_dir, idle_timeout): |
802 | 153 | strport = "tcp:%s" % port | 153 | strport = "tcp:%s" % port |
804 | 154 | factory = FTPServiceFactory(port, root, idle_timeout) | 154 | factory = FTPServiceFactory(port, root, temp_dir, idle_timeout) |
805 | 155 | return strports.service(strport, factory.ftpfactory) | 155 | return strports.service(strport, factory.ftpfactory) |
806 | 156 | 156 | ||
807 | === modified file 'src/txpkgupload/twistedsftp.py' | |||
808 | --- src/txpkgupload/twistedsftp.py 2015-01-09 15:55:08 +0000 | |||
809 | +++ src/txpkgupload/twistedsftp.py 2015-01-21 13:10:45 +0000 | |||
810 | @@ -38,13 +38,12 @@ | |||
811 | 38 | provideHandler(self.connectionClosed) | 38 | provideHandler(self.connectionClosed) |
812 | 39 | self._avatar = avatar | 39 | self._avatar = avatar |
813 | 40 | self._fs_root = avatar.fs_root | 40 | self._fs_root = avatar.fs_root |
815 | 41 | self.uploadfilesystem = UploadFileSystem(tempfile.mkdtemp()) | 41 | self.uploadfilesystem = UploadFileSystem( |
816 | 42 | tempfile.mkdtemp(dir=avatar.temp_dir)) | ||
817 | 42 | self._current_upload = self.uploadfilesystem.rootpath | 43 | self._current_upload = self.uploadfilesystem.rootpath |
818 | 43 | os.chmod(self._current_upload, 0770) | 44 | os.chmod(self._current_upload, 0770) |
821 | 44 | self.hook = Hooks( | 45 | self.hook = Hooks(self._fs_root, perms='g+rws', prefix='-sftp') |
820 | 45 | self._fs_root, "ubuntu", perms='g+rws', prefix='-sftp') | ||
822 | 46 | self.hook.new_client_hook(self._current_upload, 0, 0) | 46 | self.hook.new_client_hook(self._current_upload, 0, 0) |
823 | 47 | self.hook.auth_verify_hook(self._current_upload, None, None) | ||
824 | 48 | 47 | ||
825 | 49 | def gotVersion(self, other_version, ext_data): | 48 | def gotVersion(self, other_version, ext_data): |
826 | 50 | return {} | 49 | return {} |