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