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