##// END OF EJS Templates
inotify: server: remove wpath method...
Nicolas Dumazet -
r8953:e1d119f4 default
parent child Browse files
Show More
@@ -1,828 +1,822 b''
1 1 # server.py - inotify status server
2 2 #
3 3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2, incorporated herein by reference.
8 8
9 9 from mercurial.i18n import _
10 10 from mercurial import osutil, util
11 11 import common
12 12 import errno, os, select, socket, stat, struct, sys, tempfile, time
13 13
14 14 try:
15 15 import linux as inotify
16 16 from linux import watcher
17 17 except ImportError:
18 18 raise
19 19
20 20 class AlreadyStartedException(Exception): pass
21 21
22 22 def join(a, b):
23 23 if a:
24 24 if a[-1] == '/':
25 25 return a + b
26 26 return a + '/' + b
27 27 return b
28 28
29 29 def split(path):
30 30 c = path.rfind('/')
31 31 if c == -1:
32 32 return '', path
33 33 return path[:c], path[c+1:]
34 34
35 35 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
36 36
37 37 def walkrepodirs(repo):
38 38 '''Iterate over all subdirectories of this repo.
39 39 Exclude the .hg directory, any nested repos, and ignored dirs.'''
40 40 rootslash = repo.root + os.sep
41 41
42 42 def walkit(dirname, top):
43 43 fullpath = rootslash + dirname
44 44 try:
45 45 for name, kind in osutil.listdir(fullpath):
46 46 if kind == stat.S_IFDIR:
47 47 if name == '.hg':
48 48 if not top:
49 49 return
50 50 else:
51 51 d = join(dirname, name)
52 52 if repo.dirstate._ignore(d):
53 53 continue
54 54 for subdir in walkit(d, False):
55 55 yield subdir
56 56 except OSError, err:
57 57 if err.errno not in walk_ignored_errors:
58 58 raise
59 59 yield fullpath
60 60
61 61 return walkit('', True)
62 62
63 63 def walk(repo, root):
64 64 '''Like os.walk, but only yields regular files.'''
65 65
66 66 # This function is critical to performance during startup.
67 67
68 68 rootslash = repo.root + os.sep
69 69
70 70 def walkit(root, reporoot):
71 71 files, dirs = [], []
72 72
73 73 try:
74 74 fullpath = rootslash + root
75 75 for name, kind in osutil.listdir(fullpath):
76 76 if kind == stat.S_IFDIR:
77 77 if name == '.hg':
78 78 if not reporoot:
79 79 return
80 80 else:
81 81 dirs.append(name)
82 82 path = join(root, name)
83 83 if repo.dirstate._ignore(path):
84 84 continue
85 85 for result in walkit(path, False):
86 86 yield result
87 87 elif kind in (stat.S_IFREG, stat.S_IFLNK):
88 88 files.append(name)
89 89 yield fullpath, dirs, files
90 90
91 91 except OSError, err:
92 92 if err.errno not in walk_ignored_errors:
93 93 raise
94 94
95 95 return walkit(root, root == '')
96 96
97 97 def _explain_watch_limit(ui, repo):
98 98 path = '/proc/sys/fs/inotify/max_user_watches'
99 99 try:
100 100 limit = int(file(path).read())
101 101 except IOError, err:
102 102 if err.errno != errno.ENOENT:
103 103 raise
104 104 raise util.Abort(_('this system does not seem to '
105 105 'support inotify'))
106 106 ui.warn(_('*** the current per-user limit on the number '
107 107 'of inotify watches is %s\n') % limit)
108 108 ui.warn(_('*** this limit is too low to watch every '
109 109 'directory in this repository\n'))
110 110 ui.warn(_('*** counting directories: '))
111 111 ndirs = len(list(walkrepodirs(repo)))
112 112 ui.warn(_('found %d\n') % ndirs)
113 113 newlimit = min(limit, 1024)
114 114 while newlimit < ((limit + ndirs) * 1.1):
115 115 newlimit *= 2
116 116 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
117 117 (limit, newlimit))
118 118 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
119 119 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
120 120 % repo.root)
121 121
122 122 class pollable(object):
123 123 """
124 124 Interface to support polling.
125 125 The file descriptor returned by fileno() is registered to a polling
126 126 object.
127 127 Usage:
128 128 Every tick, check if an event has happened since the last tick:
129 129 * If yes, call handle_events
130 130 * If no, call handle_timeout
131 131 """
132 132 poll_events = select.POLLIN
133 133 instances = {}
134 134 poll = select.poll()
135 135
136 136 def fileno(self):
137 137 raise NotImplementedError
138 138
139 139 def handle_events(self, events):
140 140 raise NotImplementedError
141 141
142 142 def handle_timeout(self):
143 143 raise NotImplementedError
144 144
145 145 def shutdown(self):
146 146 raise NotImplementedError
147 147
148 148 def register(self, timeout):
149 149 fd = self.fileno()
150 150
151 151 pollable.poll.register(fd, pollable.poll_events)
152 152 pollable.instances[fd] = self
153 153
154 154 self.registered = True
155 155 self.timeout = timeout
156 156
157 157 def unregister(self):
158 158 pollable.poll.unregister(self)
159 159 self.registered = False
160 160
161 161 @classmethod
162 162 def run(cls):
163 163 while True:
164 164 timeout = None
165 165 timeobj = None
166 166 for obj in cls.instances.itervalues():
167 167 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
168 168 timeout, timeobj = obj.timeout, obj
169 169 try:
170 170 events = cls.poll.poll(timeout)
171 171 except select.error, err:
172 172 if err[0] == errno.EINTR:
173 173 continue
174 174 raise
175 175 if events:
176 176 by_fd = {}
177 177 for fd, event in events:
178 178 by_fd.setdefault(fd, []).append(event)
179 179
180 180 for fd, events in by_fd.iteritems():
181 181 cls.instances[fd].handle_pollevents(events)
182 182
183 183 elif timeobj:
184 184 timeobj.handle_timeout()
185 185
186 186 def eventaction(code):
187 187 """
188 188 Decorator to help handle events in repowatcher
189 189 """
190 190 def decorator(f):
191 191 def wrapper(self, wpath):
192 192 if code == 'm' and wpath in self.lastevent and \
193 193 self.lastevent[wpath] in 'cm':
194 194 return
195 195 self.lastevent[wpath] = code
196 196 self.timeout = 250
197 197
198 198 f(self, wpath)
199 199
200 200 wrapper.func_name = f.func_name
201 201 return wrapper
202 202 return decorator
203 203
204 204 class repowatcher(pollable):
205 205 """
206 206 Watches inotify events
207 207 """
208 208 statuskeys = 'almr!?'
209 209 mask = (
210 210 inotify.IN_ATTRIB |
211 211 inotify.IN_CREATE |
212 212 inotify.IN_DELETE |
213 213 inotify.IN_DELETE_SELF |
214 214 inotify.IN_MODIFY |
215 215 inotify.IN_MOVED_FROM |
216 216 inotify.IN_MOVED_TO |
217 217 inotify.IN_MOVE_SELF |
218 218 inotify.IN_ONLYDIR |
219 219 inotify.IN_UNMOUNT |
220 220 0)
221 221
222 222 def __init__(self, ui, repo):
223 223 self.ui = ui
224 224 self.repo = repo
225 225 self.wprefix = self.repo.wjoin('')
226 226 try:
227 227 self.watcher = watcher.watcher()
228 228 except OSError, err:
229 229 raise util.Abort(_('inotify service not available: %s') %
230 230 err.strerror)
231 231 self.threshold = watcher.threshold(self.watcher)
232 232 self.fileno = self.watcher.fileno
233 233
234 234 self.tree = {}
235 235 self.statcache = {}
236 236 self.statustrees = dict([(s, {}) for s in self.statuskeys])
237 237
238 238 self.last_event = None
239 239
240 240 self.lastevent = {}
241 241
242 242 self.register(timeout=None)
243 243
244 244 self.ds_info = self.dirstate_info()
245 245 self.handle_timeout()
246 246 self.scan()
247 247
248 248 def event_time(self):
249 249 last = self.last_event
250 250 now = time.time()
251 251 self.last_event = now
252 252
253 253 if last is None:
254 254 return 'start'
255 255 delta = now - last
256 256 if delta < 5:
257 257 return '+%.3f' % delta
258 258 if delta < 50:
259 259 return '+%.2f' % delta
260 260 return '+%.1f' % delta
261 261
262 262 def dirstate_info(self):
263 263 try:
264 264 st = os.lstat(self.repo.join('dirstate'))
265 265 return st.st_mtime, st.st_ino
266 266 except OSError, err:
267 267 if err.errno != errno.ENOENT:
268 268 raise
269 269 return 0, 0
270 270
271 271 def add_watch(self, path, mask):
272 272 if not path:
273 273 return
274 274 if self.watcher.path(path) is None:
275 275 if self.ui.debugflag:
276 276 self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
277 277 try:
278 278 self.watcher.add(path, mask)
279 279 except OSError, err:
280 280 if err.errno in (errno.ENOENT, errno.ENOTDIR):
281 281 return
282 282 if err.errno != errno.ENOSPC:
283 283 raise
284 284 _explain_watch_limit(self.ui, self.repo)
285 285
286 286 def setup(self):
287 287 self.ui.note(_('watching directories under %r\n') % self.repo.root)
288 288 self.add_watch(self.repo.path, inotify.IN_DELETE)
289 289 self.check_dirstate()
290 290
291 def wpath(self, evt):
292 path = evt.fullpath
293 if path == self.repo.root:
294 return ''
295 if path.startswith(self.wprefix):
296 return path[len(self.wprefix):]
297 raise 'wtf? ' + path
298
299 291 def dir(self, tree, path):
300 292 if path:
301 293 for name in path.split('/'):
302 294 tree = tree.setdefault(name, {})
303 295 return tree
304 296
305 297 def lookup(self, path, tree):
306 298 if path:
307 299 try:
308 300 for name in path.split('/'):
309 301 tree = tree[name]
310 302 except KeyError:
311 303 return 'x'
312 304 except TypeError:
313 305 return 'd'
314 306 return tree
315 307
316 308 def filestatus(self, fn, st):
317 309 try:
318 310 type_, mode, size, time = self.repo.dirstate._map[fn][:4]
319 311 except KeyError:
320 312 type_ = '?'
321 313 if type_ == 'n':
322 314 st_mode, st_size, st_mtime = st
323 315 if size == -1:
324 316 return 'l'
325 317 if size and (size != st_size or (mode ^ st_mode) & 0100):
326 318 return 'm'
327 319 if time != int(st_mtime):
328 320 return 'l'
329 321 return 'n'
330 322 if type_ == '?' and self.repo.dirstate._ignore(fn):
331 323 return 'i'
332 324 return type_
333 325
334 326 def updatefile(self, wfn, osstat):
335 327 '''
336 328 update the file entry of an existing file.
337 329
338 330 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
339 331 '''
340 332
341 333 self._updatestatus(wfn, self.filestatus(wfn, osstat))
342 334
343 335 def deletefile(self, wfn, oldstatus):
344 336 '''
345 337 update the entry of a file which has been deleted.
346 338
347 339 oldstatus: char in statuskeys, status of the file before deletion
348 340 '''
349 341 if oldstatus == 'r':
350 342 newstatus = 'r'
351 343 elif oldstatus in 'almn':
352 344 newstatus = '!'
353 345 else:
354 346 newstatus = None
355 347
356 348 self.statcache.pop(wfn, None)
357 349 self._updatestatus(wfn, newstatus)
358 350
359 351 def _updatestatus(self, wfn, newstatus):
360 352 '''
361 353 Update the stored status of a file or directory.
362 354
363 355 newstatus: - char in (statuskeys + 'ni'), new status to apply.
364 356 - or None, to stop tracking wfn
365 357 '''
366 358 root, fn = split(wfn)
367 359 d = self.dir(self.tree, root)
368 360
369 361 oldstatus = d.get(fn)
370 362 # oldstatus can be either:
371 363 # - None : fn is new
372 364 # - a char in statuskeys: fn is a (tracked) file
373 365 # - a dict: fn is a directory
374 366 isdir = isinstance(oldstatus, dict)
375 367
376 368 if self.ui.debugflag and oldstatus != newstatus:
377 369 if isdir:
378 370 self.ui.note(_('status: %r dir(%d) -> %s\n') %
379 371 (wfn, len(oldstatus), newstatus))
380 372 else:
381 373 self.ui.note(_('status: %r %s -> %s\n') %
382 374 (wfn, oldstatus, newstatus))
383 375 if not isdir:
384 376 if oldstatus and oldstatus in self.statuskeys \
385 377 and oldstatus != newstatus:
386 378 del self.dir(self.statustrees[oldstatus], root)[fn]
387 379 if newstatus and newstatus != 'i':
388 380 d[fn] = newstatus
389 381 if newstatus in self.statuskeys:
390 382 dd = self.dir(self.statustrees[newstatus], root)
391 383 if oldstatus != newstatus or fn not in dd:
392 384 dd[fn] = newstatus
393 385 else:
394 386 d.pop(fn, None)
395 387
396 388
397 389 def check_deleted(self, key):
398 390 # Files that had been deleted but were present in the dirstate
399 391 # may have vanished from the dirstate; we must clean them up.
400 392 nuke = []
401 393 for wfn, ignore in self.walk(key, self.statustrees[key]):
402 394 if wfn not in self.repo.dirstate:
403 395 nuke.append(wfn)
404 396 for wfn in nuke:
405 397 root, fn = split(wfn)
406 398 del self.dir(self.statustrees[key], root)[fn]
407 399 del self.dir(self.tree, root)[fn]
408 400
409 401 def scan(self, topdir=''):
410 402 ds = self.repo.dirstate._map.copy()
411 403 self.add_watch(join(self.repo.root, topdir), self.mask)
412 404 for root, dirs, files in walk(self.repo, topdir):
413 405 for d in dirs:
414 406 self.add_watch(join(root, d), self.mask)
415 407 wroot = root[len(self.wprefix):]
416 408 for fn in files:
417 409 wfn = join(wroot, fn)
418 410 self.updatefile(wfn, self.getstat(wfn))
419 411 ds.pop(wfn, None)
420 412 wtopdir = topdir
421 413 if wtopdir and wtopdir[-1] != '/':
422 414 wtopdir += '/'
423 415 for wfn, state in ds.iteritems():
424 416 if not wfn.startswith(wtopdir):
425 417 continue
426 418 try:
427 419 st = self.stat(wfn)
428 420 except OSError:
429 421 status = state[0]
430 422 self.deletefile(wfn, status)
431 423 else:
432 424 self.updatefile(wfn, st)
433 425 self.check_deleted('!')
434 426 self.check_deleted('r')
435 427
436 428 def check_dirstate(self):
437 429 ds_info = self.dirstate_info()
438 430 if ds_info == self.ds_info:
439 431 return
440 432 self.ds_info = ds_info
441 433 if not self.ui.debugflag:
442 434 self.last_event = None
443 435 self.ui.note(_('%s dirstate reload\n') % self.event_time())
444 436 self.repo.dirstate.invalidate()
445 437 self.handle_timeout()
446 438 self.scan()
447 439 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
448 440
449 441 def walk(self, states, tree, prefix=''):
450 442 # This is the "inner loop" when talking to the client.
451 443
452 444 for name, val in tree.iteritems():
453 445 path = join(prefix, name)
454 446 try:
455 447 if val in states:
456 448 yield path, val
457 449 except TypeError:
458 450 for p in self.walk(states, val, path):
459 451 yield p
460 452
461 453 def update_hgignore(self):
462 454 # An update of the ignore file can potentially change the
463 455 # states of all unknown and ignored files.
464 456
465 457 # XXX If the user has other ignore files outside the repo, or
466 458 # changes their list of ignore files at run time, we'll
467 459 # potentially never see changes to them. We could get the
468 460 # client to report to us what ignore data they're using.
469 461 # But it's easier to do nothing than to open that can of
470 462 # worms.
471 463
472 464 if '_ignore' in self.repo.dirstate.__dict__:
473 465 delattr(self.repo.dirstate, '_ignore')
474 466 self.ui.note(_('rescanning due to .hgignore change\n'))
475 467 self.handle_timeout()
476 468 self.scan()
477 469
478 470 def getstat(self, wpath):
479 471 try:
480 472 return self.statcache[wpath]
481 473 except KeyError:
482 474 try:
483 475 return self.stat(wpath)
484 476 except OSError, err:
485 477 if err.errno != errno.ENOENT:
486 478 raise
487 479
488 480 def stat(self, wpath):
489 481 try:
490 482 st = os.lstat(join(self.wprefix, wpath))
491 483 ret = st.st_mode, st.st_size, st.st_mtime
492 484 self.statcache[wpath] = ret
493 485 return ret
494 486 except OSError:
495 487 self.statcache.pop(wpath, None)
496 488 raise
497 489
498 490 @eventaction('c')
499 491 def created(self, wpath):
500 492 if wpath == '.hgignore':
501 493 self.update_hgignore()
502 494 try:
503 495 st = self.stat(wpath)
504 496 if stat.S_ISREG(st[0]):
505 497 self.updatefile(wpath, st)
506 498 except OSError:
507 499 pass
508 500
509 501 @eventaction('m')
510 502 def modified(self, wpath):
511 503 if wpath == '.hgignore':
512 504 self.update_hgignore()
513 505 try:
514 506 st = self.stat(wpath)
515 507 if stat.S_ISREG(st[0]):
516 508 if self.repo.dirstate[wpath] in 'lmn':
517 509 self.updatefile(wpath, st)
518 510 except OSError:
519 511 pass
520 512
521 513 @eventaction('d')
522 514 def deleted(self, wpath):
523 515 if wpath == '.hgignore':
524 516 self.update_hgignore()
525 517 elif wpath.startswith('.hg/'):
526 518 if wpath == '.hg/wlock':
527 519 self.check_dirstate()
528 520 return
529 521
530 522 self.deletefile(wpath, self.repo.dirstate[wpath])
531 523
532 524 def process_create(self, wpath, evt):
533 525 if self.ui.debugflag:
534 526 self.ui.note(_('%s event: created %s\n') %
535 527 (self.event_time(), wpath))
536 528
537 529 if evt.mask & inotify.IN_ISDIR:
538 530 self.scan(wpath)
539 531 else:
540 532 self.created(wpath)
541 533
542 534 def process_delete(self, wpath, evt):
543 535 if self.ui.debugflag:
544 536 self.ui.note(_('%s event: deleted %s\n') %
545 537 (self.event_time(), wpath))
546 538
547 539 if evt.mask & inotify.IN_ISDIR:
548 540 tree = self.dir(self.tree, wpath).copy()
549 541 for wfn, ignore in self.walk('?', tree):
550 542 self.deletefile(join(wpath, wfn), '?')
551 543 self.scan(wpath)
552 544 else:
553 545 self.deleted(wpath)
554 546
555 547 def process_modify(self, wpath, evt):
556 548 if self.ui.debugflag:
557 549 self.ui.note(_('%s event: modified %s\n') %
558 550 (self.event_time(), wpath))
559 551
560 552 if not (evt.mask & inotify.IN_ISDIR):
561 553 self.modified(wpath)
562 554
563 555 def process_unmount(self, evt):
564 556 self.ui.warn(_('filesystem containing %s was unmounted\n') %
565 557 evt.fullpath)
566 558 sys.exit(0)
567 559
568 560 def handle_pollevents(self, events):
569 561 if self.ui.debugflag:
570 562 self.ui.note(_('%s readable: %d bytes\n') %
571 563 (self.event_time(), self.threshold.readable()))
572 564 if not self.threshold():
573 565 if self.registered:
574 566 if self.ui.debugflag:
575 567 self.ui.note(_('%s below threshold - unhooking\n') %
576 568 (self.event_time()))
577 569 self.unregister()
578 570 self.timeout = 250
579 571 else:
580 572 self.read_events()
581 573
582 574 def read_events(self, bufsize=None):
583 575 events = self.watcher.read(bufsize)
584 576 if self.ui.debugflag:
585 577 self.ui.note(_('%s reading %d events\n') %
586 578 (self.event_time(), len(events)))
587 579 for evt in events:
588 wpath = self.wpath(evt)
580 assert evt.fullpath.startswith(self.wprefix)
581 wpath = evt.fullpath[len(self.wprefix):]
582
589 583 if evt.mask & inotify.IN_UNMOUNT:
590 584 self.process_unmount(wpath, evt)
591 585 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
592 586 self.process_modify(wpath, evt)
593 587 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
594 588 inotify.IN_MOVED_FROM):
595 589 self.process_delete(wpath, evt)
596 590 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
597 591 self.process_create(wpath, evt)
598 592
599 593 self.lastevent.clear()
600 594
601 595 def handle_timeout(self):
602 596 if not self.registered:
603 597 if self.ui.debugflag:
604 598 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
605 599 (self.event_time(), self.threshold.readable()))
606 600 self.read_events(0)
607 601 self.register(timeout=None)
608 602
609 603 self.timeout = None
610 604
611 605 def shutdown(self):
612 606 self.watcher.close()
613 607
614 608 def debug(self):
615 609 """
616 610 Returns a sorted list of relatives paths currently watched,
617 611 for debugging purposes.
618 612 """
619 613 return sorted(tuple[0][len(self.wprefix):] for tuple in self.watcher)
620 614
621 615 class server(pollable):
622 616 """
623 617 Listens for client queries on unix socket inotify.sock
624 618 """
625 619 def __init__(self, ui, repo, repowatcher, timeout):
626 620 self.ui = ui
627 621 self.repo = repo
628 622 self.repowatcher = repowatcher
629 623 self.sock = socket.socket(socket.AF_UNIX)
630 624 self.sockpath = self.repo.join('inotify.sock')
631 625 self.realsockpath = None
632 626 try:
633 627 self.sock.bind(self.sockpath)
634 628 except socket.error, err:
635 629 if err[0] == errno.EADDRINUSE:
636 630 raise AlreadyStartedException(_('could not start server: %s')
637 631 % err[1])
638 632 if err[0] == "AF_UNIX path too long":
639 633 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
640 634 self.realsockpath = os.path.join(tempdir, "inotify.sock")
641 635 try:
642 636 self.sock.bind(self.realsockpath)
643 637 os.symlink(self.realsockpath, self.sockpath)
644 638 except (OSError, socket.error), inst:
645 639 try:
646 640 os.unlink(self.realsockpath)
647 641 except:
648 642 pass
649 643 os.rmdir(tempdir)
650 644 if inst.errno == errno.EEXIST:
651 645 raise AlreadyStartedException(_('could not start server: %s')
652 646 % inst.strerror)
653 647 raise
654 648 else:
655 649 raise
656 650 self.sock.listen(5)
657 651 self.fileno = self.sock.fileno
658 652 self.register(timeout=timeout)
659 653
660 654 def handle_timeout(self):
661 655 pass
662 656
663 657 def answer_stat_query(self, cs):
664 658 names = cs.read().split('\0')
665 659
666 660 states = names.pop()
667 661
668 662 self.ui.note(_('answering query for %r\n') % states)
669 663
670 664 if self.repowatcher.timeout:
671 665 # We got a query while a rescan is pending. Make sure we
672 666 # rescan before responding, or we could give back a wrong
673 667 # answer.
674 668 self.repowatcher.handle_timeout()
675 669
676 670 if not names:
677 671 def genresult(states, tree):
678 672 for fn, state in self.repowatcher.walk(states, tree):
679 673 yield fn
680 674 else:
681 675 def genresult(states, tree):
682 676 for fn in names:
683 677 l = self.repowatcher.lookup(fn, tree)
684 678 try:
685 679 if l in states:
686 680 yield fn
687 681 except TypeError:
688 682 for f, s in self.repowatcher.walk(states, l, fn):
689 683 yield f
690 684
691 685 return ['\0'.join(r) for r in [
692 686 genresult('l', self.repowatcher.statustrees['l']),
693 687 genresult('m', self.repowatcher.statustrees['m']),
694 688 genresult('a', self.repowatcher.statustrees['a']),
695 689 genresult('r', self.repowatcher.statustrees['r']),
696 690 genresult('!', self.repowatcher.statustrees['!']),
697 691 '?' in states
698 692 and genresult('?', self.repowatcher.statustrees['?'])
699 693 or [],
700 694 [],
701 695 'c' in states and genresult('n', self.repowatcher.tree) or [],
702 696 ]]
703 697
704 698 def answer_dbug_query(self):
705 699 return ['\0'.join(self.repowatcher.debug())]
706 700
707 701 def handle_pollevents(self, events):
708 702 for e in events:
709 703 self.handle_pollevent()
710 704
711 705 def handle_pollevent(self):
712 706 sock, addr = self.sock.accept()
713 707
714 708 cs = common.recvcs(sock)
715 709 version = ord(cs.read(1))
716 710
717 711 if version != common.version:
718 712 self.ui.warn(_('received query from incompatible client '
719 713 'version %d\n') % version)
720 714 try:
721 715 # try to send back our version to the client
722 716 # this way, the client too is informed of the mismatch
723 717 sock.sendall(chr(common.version))
724 718 except:
725 719 pass
726 720 return
727 721
728 722 type = cs.read(4)
729 723
730 724 if type == 'STAT':
731 725 results = self.answer_stat_query(cs)
732 726 elif type == 'DBUG':
733 727 results = self.answer_dbug_query()
734 728 else:
735 729 self.ui.warn(_('unrecognized query type: %s\n') % type)
736 730 return
737 731
738 732 try:
739 733 try:
740 734 v = chr(common.version)
741 735
742 736 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
743 737 *map(len, results)))
744 738 sock.sendall(''.join(results))
745 739 finally:
746 740 sock.shutdown(socket.SHUT_WR)
747 741 except socket.error, err:
748 742 if err[0] != errno.EPIPE:
749 743 raise
750 744
751 745 def shutdown(self):
752 746 self.sock.close()
753 747 try:
754 748 os.unlink(self.sockpath)
755 749 if self.realsockpath:
756 750 os.unlink(self.realsockpath)
757 751 os.rmdir(os.path.dirname(self.realsockpath))
758 752 except OSError, err:
759 753 if err.errno != errno.ENOENT:
760 754 raise
761 755
762 756 class master(object):
763 757 def __init__(self, ui, repo, timeout=None):
764 758 self.ui = ui
765 759 self.repo = repo
766 760 self.repowatcher = repowatcher(ui, repo)
767 761 self.server = server(ui, repo, self.repowatcher, timeout)
768 762
769 763 def shutdown(self):
770 764 for obj in pollable.instances.itervalues():
771 765 obj.shutdown()
772 766
773 767 def run(self):
774 768 self.repowatcher.setup()
775 769 self.ui.note(_('finished setup\n'))
776 770 if os.getenv('TIME_STARTUP'):
777 771 sys.exit(0)
778 772 pollable.run()
779 773
780 774 def start(ui, repo):
781 775 def closefds(ignore):
782 776 # (from python bug #1177468)
783 777 # close all inherited file descriptors
784 778 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
785 779 # a file descriptor is kept internally as os._urandomfd (created on demand
786 780 # the first time os.urandom() is called), and should not be closed
787 781 try:
788 782 os.urandom(4)
789 783 urandom_fd = getattr(os, '_urandomfd', None)
790 784 except AttributeError:
791 785 urandom_fd = None
792 786 ignore.append(urandom_fd)
793 787 for fd in range(3, 256):
794 788 if fd in ignore:
795 789 continue
796 790 try:
797 791 os.close(fd)
798 792 except OSError:
799 793 pass
800 794
801 795 m = master(ui, repo)
802 796 sys.stdout.flush()
803 797 sys.stderr.flush()
804 798
805 799 pid = os.fork()
806 800 if pid:
807 801 return pid
808 802
809 803 closefds(pollable.instances.keys())
810 804 os.setsid()
811 805
812 806 fd = os.open('/dev/null', os.O_RDONLY)
813 807 os.dup2(fd, 0)
814 808 if fd > 0:
815 809 os.close(fd)
816 810
817 811 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
818 812 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
819 813 os.dup2(fd, 1)
820 814 os.dup2(fd, 2)
821 815 if fd > 2:
822 816 os.close(fd)
823 817
824 818 try:
825 819 m.run()
826 820 finally:
827 821 m.shutdown()
828 822 os._exit(0)
General Comments 0
You need to be logged in to leave comments. Login now