##// END OF EJS Templates
inotify: Coding Style: name classes in lowercase.
Nicolas Dumazet -
r8385:1536501a default
parent child Browse files
Show More
@@ -1,125 +1,125 b''
1 # __init__.py - inotify-based status acceleration for Linux
1 # __init__.py - inotify-based status acceleration for Linux
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 '''inotify-based status acceleration for Linux systems
9 '''inotify-based status acceleration for Linux systems
10 '''
10 '''
11
11
12 # todo: socket permissions
12 # todo: socket permissions
13
13
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15 from mercurial import cmdutil, util
15 from mercurial import cmdutil, util
16 import client, errno, os, server, socket
16 import client, errno, os, server, socket
17 from weakref import proxy
17 from weakref import proxy
18
18
19 def serve(ui, repo, **opts):
19 def serve(ui, repo, **opts):
20 '''start an inotify server for this repository'''
20 '''start an inotify server for this repository'''
21 timeout = opts.get('timeout')
21 timeout = opts.get('timeout')
22 if timeout:
22 if timeout:
23 timeout = float(timeout) * 1e3
23 timeout = float(timeout) * 1e3
24
24
25 class service:
25 class service:
26 def init(self):
26 def init(self):
27 try:
27 try:
28 self.master = server.Master(ui, repo, timeout)
28 self.master = server.master(ui, repo, timeout)
29 except server.AlreadyStartedException, inst:
29 except server.AlreadyStartedException, inst:
30 raise util.Abort(str(inst))
30 raise util.Abort(str(inst))
31
31
32 def run(self):
32 def run(self):
33 try:
33 try:
34 self.master.run()
34 self.master.run()
35 finally:
35 finally:
36 self.master.shutdown()
36 self.master.shutdown()
37
37
38 service = service()
38 service = service()
39 cmdutil.service(opts, initfn=service.init, runfn=service.run)
39 cmdutil.service(opts, initfn=service.init, runfn=service.run)
40
40
41 def reposetup(ui, repo):
41 def reposetup(ui, repo):
42 if not hasattr(repo, 'dirstate'):
42 if not hasattr(repo, 'dirstate'):
43 return
43 return
44
44
45 # XXX: weakref until hg stops relying on __del__
45 # XXX: weakref until hg stops relying on __del__
46 repo = proxy(repo)
46 repo = proxy(repo)
47
47
48 class inotifydirstate(repo.dirstate.__class__):
48 class inotifydirstate(repo.dirstate.__class__):
49 # Set to True if we're the inotify server, so we don't attempt
49 # Set to True if we're the inotify server, so we don't attempt
50 # to recurse.
50 # to recurse.
51 inotifyserver = False
51 inotifyserver = False
52
52
53 def status(self, match, ignored, clean, unknown=True):
53 def status(self, match, ignored, clean, unknown=True):
54 files = match.files()
54 files = match.files()
55 if '.' in files:
55 if '.' in files:
56 files = []
56 files = []
57 try:
57 try:
58 if not ignored and not self.inotifyserver:
58 if not ignored and not self.inotifyserver:
59 result = client.query(ui, repo, files, match, False,
59 result = client.query(ui, repo, files, match, False,
60 clean, unknown)
60 clean, unknown)
61 if result and ui.config('inotify', 'debug'):
61 if result and ui.config('inotify', 'debug'):
62 r2 = super(inotifydirstate, self).status(
62 r2 = super(inotifydirstate, self).status(
63 match, False, clean, unknown)
63 match, False, clean, unknown)
64 for c,a,b in zip('LMARDUIC', result, r2):
64 for c,a,b in zip('LMARDUIC', result, r2):
65 for f in a:
65 for f in a:
66 if f not in b:
66 if f not in b:
67 ui.warn('*** inotify: %s +%s\n' % (c, f))
67 ui.warn('*** inotify: %s +%s\n' % (c, f))
68 for f in b:
68 for f in b:
69 if f not in a:
69 if f not in a:
70 ui.warn('*** inotify: %s -%s\n' % (c, f))
70 ui.warn('*** inotify: %s -%s\n' % (c, f))
71 result = r2
71 result = r2
72
72
73 if result is not None:
73 if result is not None:
74 return result
74 return result
75 except (OSError, socket.error), err:
75 except (OSError, socket.error), err:
76 autostart = ui.configbool('inotify', 'autostart', True)
76 autostart = ui.configbool('inotify', 'autostart', True)
77
77
78 if err[0] == errno.ECONNREFUSED:
78 if err[0] == errno.ECONNREFUSED:
79 ui.warn(_('(found dead inotify server socket; '
79 ui.warn(_('(found dead inotify server socket; '
80 'removing it)\n'))
80 'removing it)\n'))
81 os.unlink(repo.join('inotify.sock'))
81 os.unlink(repo.join('inotify.sock'))
82 if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
82 if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
83 ui.debug(_('(starting inotify server)\n'))
83 ui.debug(_('(starting inotify server)\n'))
84 try:
84 try:
85 try:
85 try:
86 server.start(ui, repo)
86 server.start(ui, repo)
87 except server.AlreadyStartedException, inst:
87 except server.AlreadyStartedException, inst:
88 # another process may have started its own
88 # another process may have started its own
89 # inotify server while this one was starting.
89 # inotify server while this one was starting.
90 ui.debug(str(inst))
90 ui.debug(str(inst))
91 except Exception, inst:
91 except Exception, inst:
92 ui.warn(_('could not start inotify server: '
92 ui.warn(_('could not start inotify server: '
93 '%s\n') % inst)
93 '%s\n') % inst)
94 else:
94 else:
95 # server is started, send query again
95 # server is started, send query again
96 try:
96 try:
97 return client.query(ui, repo, files, match,
97 return client.query(ui, repo, files, match,
98 ignored, clean, unknown)
98 ignored, clean, unknown)
99 except socket.error, err:
99 except socket.error, err:
100 ui.warn(_('could not talk to new inotify '
100 ui.warn(_('could not talk to new inotify '
101 'server: %s\n') % err[-1])
101 'server: %s\n') % err[-1])
102 elif err[0] in (errno.ECONNREFUSED, errno.ENOENT):
102 elif err[0] in (errno.ECONNREFUSED, errno.ENOENT):
103 # silently ignore normal errors if autostart is False
103 # silently ignore normal errors if autostart is False
104 ui.debug(_('(inotify server not running)\n'))
104 ui.debug(_('(inotify server not running)\n'))
105 else:
105 else:
106 ui.warn(_('failed to contact inotify server: %s\n')
106 ui.warn(_('failed to contact inotify server: %s\n')
107 % err[-1])
107 % err[-1])
108 ui.traceback()
108 ui.traceback()
109 # replace by old status function
109 # replace by old status function
110 self.status = super(inotifydirstate, self).status
110 self.status = super(inotifydirstate, self).status
111
111
112 return super(inotifydirstate, self).status(
112 return super(inotifydirstate, self).status(
113 match, ignored, clean, unknown)
113 match, ignored, clean, unknown)
114
114
115 repo.dirstate.__class__ = inotifydirstate
115 repo.dirstate.__class__ = inotifydirstate
116
116
117 cmdtable = {
117 cmdtable = {
118 '^inserve':
118 '^inserve':
119 (serve,
119 (serve,
120 [('d', 'daemon', None, _('run server in background')),
120 [('d', 'daemon', None, _('run server in background')),
121 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
121 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
122 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
122 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
123 ('', 'pid-file', '', _('name of file to write process ID to'))],
123 ('', 'pid-file', '', _('name of file to write process ID to'))],
124 _('hg inserve [OPT]...')),
124 _('hg inserve [OPT]...')),
125 }
125 }
@@ -1,335 +1,335 b''
1 # watcher.py - high-level interfaces to the Linux inotify subsystem
1 # watcher.py - high-level interfaces to the Linux inotify subsystem
2
2
3 # Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 # This library is free software; you can redistribute it and/or modify
5 # This library is free software; you can redistribute it and/or modify
6 # it under the terms of version 2.1 of the GNU Lesser General Public
6 # it under the terms of version 2.1 of the GNU Lesser General Public
7 # License, incorporated herein by reference.
7 # License, incorporated herein by reference.
8
8
9 '''High-level interfaces to the Linux inotify subsystem.
9 '''High-level interfaces to the Linux inotify subsystem.
10
10
11 The inotify subsystem provides an efficient mechanism for file status
11 The inotify subsystem provides an efficient mechanism for file status
12 monitoring and change notification.
12 monitoring and change notification.
13
13
14 The Watcher class hides the low-level details of the inotify
14 The watcher class hides the low-level details of the inotify
15 interface, and provides a Pythonic wrapper around it. It generates
15 interface, and provides a Pythonic wrapper around it. It generates
16 events that provide somewhat more information than raw inotify makes
16 events that provide somewhat more information than raw inotify makes
17 available.
17 available.
18
18
19 The AutoWatcher class is more useful, as it automatically watches
19 The autowatcher class is more useful, as it automatically watches
20 newly-created directories on your behalf.'''
20 newly-created directories on your behalf.'''
21
21
22 __author__ = "Bryan O'Sullivan <bos@serpentine.com>"
22 __author__ = "Bryan O'Sullivan <bos@serpentine.com>"
23
23
24 import _inotify as inotify
24 import _inotify as inotify
25 import array
25 import array
26 import errno
26 import errno
27 import fcntl
27 import fcntl
28 import os
28 import os
29 import termios
29 import termios
30
30
31
31
32 class Event(object):
32 class event(object):
33 '''Derived inotify event class.
33 '''Derived inotify event class.
34
34
35 The following fields are available:
35 The following fields are available:
36
36
37 mask: event mask, indicating what kind of event this is
37 mask: event mask, indicating what kind of event this is
38
38
39 cookie: rename cookie, if a rename-related event
39 cookie: rename cookie, if a rename-related event
40
40
41 path: path of the directory in which the event occurred
41 path: path of the directory in which the event occurred
42
42
43 name: name of the directory entry to which the event occurred
43 name: name of the directory entry to which the event occurred
44 (may be None if the event happened to a watched directory)
44 (may be None if the event happened to a watched directory)
45
45
46 fullpath: complete path at which the event occurred
46 fullpath: complete path at which the event occurred
47
47
48 wd: watch descriptor that triggered this event'''
48 wd: watch descriptor that triggered this event'''
49
49
50 __slots__ = (
50 __slots__ = (
51 'cookie',
51 'cookie',
52 'fullpath',
52 'fullpath',
53 'mask',
53 'mask',
54 'name',
54 'name',
55 'path',
55 'path',
56 'raw',
56 'raw',
57 'wd',
57 'wd',
58 )
58 )
59
59
60 def __init__(self, raw, path):
60 def __init__(self, raw, path):
61 self.path = path
61 self.path = path
62 self.raw = raw
62 self.raw = raw
63 if raw.name:
63 if raw.name:
64 self.fullpath = path + '/' + raw.name
64 self.fullpath = path + '/' + raw.name
65 else:
65 else:
66 self.fullpath = path
66 self.fullpath = path
67
67
68 self.wd = raw.wd
68 self.wd = raw.wd
69 self.mask = raw.mask
69 self.mask = raw.mask
70 self.cookie = raw.cookie
70 self.cookie = raw.cookie
71 self.name = raw.name
71 self.name = raw.name
72
72
73 def __repr__(self):
73 def __repr__(self):
74 r = repr(self.raw)
74 r = repr(self.raw)
75 return 'Event(path=' + repr(self.path) + ', ' + r[r.find('(')+1:]
75 return 'event(path=' + repr(self.path) + ', ' + r[r.find('(')+1:]
76
76
77
77
78 _event_props = {
78 _event_props = {
79 'access': 'File was accessed',
79 'access': 'File was accessed',
80 'modify': 'File was modified',
80 'modify': 'File was modified',
81 'attrib': 'Attribute of a directory entry was changed',
81 'attrib': 'Attribute of a directory entry was changed',
82 'close_write': 'File was closed after being written to',
82 'close_write': 'File was closed after being written to',
83 'close_nowrite': 'File was closed without being written to',
83 'close_nowrite': 'File was closed without being written to',
84 'open': 'File was opened',
84 'open': 'File was opened',
85 'moved_from': 'Directory entry was renamed from this name',
85 'moved_from': 'Directory entry was renamed from this name',
86 'moved_to': 'Directory entry was renamed to this name',
86 'moved_to': 'Directory entry was renamed to this name',
87 'create': 'Directory entry was created',
87 'create': 'Directory entry was created',
88 'delete': 'Directory entry was deleted',
88 'delete': 'Directory entry was deleted',
89 'delete_self': 'The watched directory entry was deleted',
89 'delete_self': 'The watched directory entry was deleted',
90 'move_self': 'The watched directory entry was renamed',
90 'move_self': 'The watched directory entry was renamed',
91 'unmount': 'Directory was unmounted, and can no longer be watched',
91 'unmount': 'Directory was unmounted, and can no longer be watched',
92 'q_overflow': 'Kernel dropped events due to queue overflow',
92 'q_overflow': 'Kernel dropped events due to queue overflow',
93 'ignored': 'Directory entry is no longer being watched',
93 'ignored': 'Directory entry is no longer being watched',
94 'isdir': 'Event occurred on a directory',
94 'isdir': 'Event occurred on a directory',
95 }
95 }
96
96
97 for k, v in _event_props.iteritems():
97 for k, v in _event_props.iteritems():
98 mask = getattr(inotify, 'IN_' + k.upper())
98 mask = getattr(inotify, 'IN_' + k.upper())
99 def getter(self):
99 def getter(self):
100 return self.mask & mask
100 return self.mask & mask
101 getter.__name__ = k
101 getter.__name__ = k
102 getter.__doc__ = v
102 getter.__doc__ = v
103 setattr(Event, k, property(getter, doc=v))
103 setattr(event, k, property(getter, doc=v))
104
104
105 del _event_props
105 del _event_props
106
106
107
107
108 class Watcher(object):
108 class watcher(object):
109 '''Provide a Pythonic interface to the low-level inotify API.
109 '''Provide a Pythonic interface to the low-level inotify API.
110
110
111 Also adds derived information to each event that is not available
111 Also adds derived information to each event that is not available
112 through the normal inotify API, such as directory name.'''
112 through the normal inotify API, such as directory name.'''
113
113
114 __slots__ = (
114 __slots__ = (
115 'fd',
115 'fd',
116 '_paths',
116 '_paths',
117 '_wds',
117 '_wds',
118 )
118 )
119
119
120 def __init__(self):
120 def __init__(self):
121 '''Create a new inotify instance.'''
121 '''Create a new inotify instance.'''
122
122
123 self.fd = inotify.init()
123 self.fd = inotify.init()
124 self._paths = {}
124 self._paths = {}
125 self._wds = {}
125 self._wds = {}
126
126
127 def fileno(self):
127 def fileno(self):
128 '''Return the file descriptor this watcher uses.
128 '''Return the file descriptor this watcher uses.
129
129
130 Useful for passing to select and poll.'''
130 Useful for passing to select and poll.'''
131
131
132 return self.fd
132 return self.fd
133
133
134 def add(self, path, mask):
134 def add(self, path, mask):
135 '''Add or modify a watch.
135 '''Add or modify a watch.
136
136
137 Return the watch descriptor added or modified.'''
137 Return the watch descriptor added or modified.'''
138
138
139 path = os.path.normpath(path)
139 path = os.path.normpath(path)
140 wd = inotify.add_watch(self.fd, path, mask)
140 wd = inotify.add_watch(self.fd, path, mask)
141 self._paths[path] = wd, mask
141 self._paths[path] = wd, mask
142 self._wds[wd] = path, mask
142 self._wds[wd] = path, mask
143 return wd
143 return wd
144
144
145 def remove(self, wd):
145 def remove(self, wd):
146 '''Remove the given watch.'''
146 '''Remove the given watch.'''
147
147
148 inotify.remove_watch(self.fd, wd)
148 inotify.remove_watch(self.fd, wd)
149 self._remove(wd)
149 self._remove(wd)
150
150
151 def _remove(self, wd):
151 def _remove(self, wd):
152 path_mask = self._wds.pop(wd, None)
152 path_mask = self._wds.pop(wd, None)
153 if path_mask is not None:
153 if path_mask is not None:
154 self._paths.pop(path_mask[0])
154 self._paths.pop(path_mask[0])
155
155
156 def path(self, path):
156 def path(self, path):
157 '''Return a (watch descriptor, event mask) pair for the given path.
157 '''Return a (watch descriptor, event mask) pair for the given path.
158
158
159 If the path is not being watched, return None.'''
159 If the path is not being watched, return None.'''
160
160
161 return self._paths.get(path)
161 return self._paths.get(path)
162
162
163 def wd(self, wd):
163 def wd(self, wd):
164 '''Return a (path, event mask) pair for the given watch descriptor.
164 '''Return a (path, event mask) pair for the given watch descriptor.
165
165
166 If the watch descriptor is not valid or not associated with
166 If the watch descriptor is not valid or not associated with
167 this watcher, return None.'''
167 this watcher, return None.'''
168
168
169 return self._wds.get(wd)
169 return self._wds.get(wd)
170
170
171 def read(self, bufsize=None):
171 def read(self, bufsize=None):
172 '''Read a list of queued inotify events.
172 '''Read a list of queued inotify events.
173
173
174 If bufsize is zero, only return those events that can be read
174 If bufsize is zero, only return those events that can be read
175 immediately without blocking. Otherwise, block until events are
175 immediately without blocking. Otherwise, block until events are
176 available.'''
176 available.'''
177
177
178 events = []
178 events = []
179 for evt in inotify.read(self.fd, bufsize):
179 for evt in inotify.read(self.fd, bufsize):
180 events.append(Event(evt, self._wds[evt.wd][0]))
180 events.append(event(evt, self._wds[evt.wd][0]))
181 if evt.mask & inotify.IN_IGNORED:
181 if evt.mask & inotify.IN_IGNORED:
182 self._remove(evt.wd)
182 self._remove(evt.wd)
183 elif evt.mask & inotify.IN_UNMOUNT:
183 elif evt.mask & inotify.IN_UNMOUNT:
184 self.close()
184 self.close()
185 return events
185 return events
186
186
187 def close(self):
187 def close(self):
188 '''Shut down this watcher.
188 '''Shut down this watcher.
189
189
190 All subsequent method calls are likely to raise exceptions.'''
190 All subsequent method calls are likely to raise exceptions.'''
191
191
192 os.close(self.fd)
192 os.close(self.fd)
193 self.fd = None
193 self.fd = None
194 self._paths = None
194 self._paths = None
195 self._wds = None
195 self._wds = None
196
196
197 def __len__(self):
197 def __len__(self):
198 '''Return the number of active watches.'''
198 '''Return the number of active watches.'''
199
199
200 return len(self._paths)
200 return len(self._paths)
201
201
202 def __iter__(self):
202 def __iter__(self):
203 '''Yield a (path, watch descriptor, event mask) tuple for each
203 '''Yield a (path, watch descriptor, event mask) tuple for each
204 entry being watched.'''
204 entry being watched.'''
205
205
206 for path, (wd, mask) in self._paths.iteritems():
206 for path, (wd, mask) in self._paths.iteritems():
207 yield path, wd, mask
207 yield path, wd, mask
208
208
209 def __del__(self):
209 def __del__(self):
210 if self.fd is not None:
210 if self.fd is not None:
211 os.close(self.fd)
211 os.close(self.fd)
212
212
213 ignored_errors = [errno.ENOENT, errno.EPERM, errno.ENOTDIR]
213 ignored_errors = [errno.ENOENT, errno.EPERM, errno.ENOTDIR]
214
214
215 def add_iter(self, path, mask, onerror=None):
215 def add_iter(self, path, mask, onerror=None):
216 '''Add or modify watches over path and its subdirectories.
216 '''Add or modify watches over path and its subdirectories.
217
217
218 Yield each added or modified watch descriptor.
218 Yield each added or modified watch descriptor.
219
219
220 To ensure that this method runs to completion, you must
220 To ensure that this method runs to completion, you must
221 iterate over all of its results, even if you do not care what
221 iterate over all of its results, even if you do not care what
222 they are. For example:
222 they are. For example:
223
223
224 for wd in w.add_iter(path, mask):
224 for wd in w.add_iter(path, mask):
225 pass
225 pass
226
226
227 By default, errors are ignored. If optional arg "onerror" is
227 By default, errors are ignored. If optional arg "onerror" is
228 specified, it should be a function; it will be called with one
228 specified, it should be a function; it will be called with one
229 argument, an OSError instance. It can report the error to
229 argument, an OSError instance. It can report the error to
230 continue with the walk, or raise the exception to abort the
230 continue with the walk, or raise the exception to abort the
231 walk.'''
231 walk.'''
232
232
233 # Add the IN_ONLYDIR flag to the event mask, to avoid a possible
233 # Add the IN_ONLYDIR flag to the event mask, to avoid a possible
234 # race when adding a subdirectory. In the time between the
234 # race when adding a subdirectory. In the time between the
235 # event being queued by the kernel and us processing it, the
235 # event being queued by the kernel and us processing it, the
236 # directory may have been deleted, or replaced with a different
236 # directory may have been deleted, or replaced with a different
237 # kind of entry with the same name.
237 # kind of entry with the same name.
238
238
239 submask = mask | inotify.IN_ONLYDIR
239 submask = mask | inotify.IN_ONLYDIR
240
240
241 try:
241 try:
242 yield self.add(path, mask)
242 yield self.add(path, mask)
243 except OSError, err:
243 except OSError, err:
244 if onerror and err.errno not in self.ignored_errors:
244 if onerror and err.errno not in self.ignored_errors:
245 onerror(err)
245 onerror(err)
246 for root, dirs, names in os.walk(path, topdown=False, onerror=onerror):
246 for root, dirs, names in os.walk(path, topdown=False, onerror=onerror):
247 for d in dirs:
247 for d in dirs:
248 try:
248 try:
249 yield self.add(root + '/' + d, submask)
249 yield self.add(root + '/' + d, submask)
250 except OSError, err:
250 except OSError, err:
251 if onerror and err.errno not in self.ignored_errors:
251 if onerror and err.errno not in self.ignored_errors:
252 onerror(err)
252 onerror(err)
253
253
254 def add_all(self, path, mask, onerror=None):
254 def add_all(self, path, mask, onerror=None):
255 '''Add or modify watches over path and its subdirectories.
255 '''Add or modify watches over path and its subdirectories.
256
256
257 Return a list of added or modified watch descriptors.
257 Return a list of added or modified watch descriptors.
258
258
259 By default, errors are ignored. If optional arg "onerror" is
259 By default, errors are ignored. If optional arg "onerror" is
260 specified, it should be a function; it will be called with one
260 specified, it should be a function; it will be called with one
261 argument, an OSError instance. It can report the error to
261 argument, an OSError instance. It can report the error to
262 continue with the walk, or raise the exception to abort the
262 continue with the walk, or raise the exception to abort the
263 walk.'''
263 walk.'''
264
264
265 return [w for w in self.add_iter(path, mask, onerror)]
265 return [w for w in self.add_iter(path, mask, onerror)]
266
266
267
267
268 class AutoWatcher(Watcher):
268 class autowatcher(watcher):
269 '''Watcher class that automatically watches newly created directories.'''
269 '''watcher class that automatically watches newly created directories.'''
270
270
271 __slots__ = (
271 __slots__ = (
272 'addfilter',
272 'addfilter',
273 )
273 )
274
274
275 def __init__(self, addfilter=None):
275 def __init__(self, addfilter=None):
276 '''Create a new inotify instance.
276 '''Create a new inotify instance.
277
277
278 This instance will automatically watch newly created
278 This instance will automatically watch newly created
279 directories.
279 directories.
280
280
281 If the optional addfilter parameter is not None, it must be a
281 If the optional addfilter parameter is not None, it must be a
282 callable that takes one parameter. It will be called each time
282 callable that takes one parameter. It will be called each time
283 a directory is about to be automatically watched. If it returns
283 a directory is about to be automatically watched. If it returns
284 True, the directory will be watched if it still exists,
284 True, the directory will be watched if it still exists,
285 otherwise, it will beb skipped.'''
285 otherwise, it will beb skipped.'''
286
286
287 super(AutoWatcher, self).__init__()
287 super(autowatcher, self).__init__()
288 self.addfilter = addfilter
288 self.addfilter = addfilter
289
289
290 _dir_create_mask = inotify.IN_ISDIR | inotify.IN_CREATE
290 _dir_create_mask = inotify.IN_ISDIR | inotify.IN_CREATE
291
291
292 def read(self, bufsize=None):
292 def read(self, bufsize=None):
293 events = super(AutoWatcher, self).read(bufsize)
293 events = super(autowatcher, self).read(bufsize)
294 for evt in events:
294 for evt in events:
295 if evt.mask & self._dir_create_mask == self._dir_create_mask:
295 if evt.mask & self._dir_create_mask == self._dir_create_mask:
296 if self.addfilter is None or self.addfilter(evt):
296 if self.addfilter is None or self.addfilter(evt):
297 parentmask = self._wds[evt.wd][1]
297 parentmask = self._wds[evt.wd][1]
298 # See note about race avoidance via IN_ONLYDIR above.
298 # See note about race avoidance via IN_ONLYDIR above.
299 mask = parentmask | inotify.IN_ONLYDIR
299 mask = parentmask | inotify.IN_ONLYDIR
300 try:
300 try:
301 self.add_all(evt.fullpath, mask)
301 self.add_all(evt.fullpath, mask)
302 except OSError, err:
302 except OSError, err:
303 if err.errno not in self.ignored_errors:
303 if err.errno not in self.ignored_errors:
304 raise
304 raise
305 return events
305 return events
306
306
307
307
308 class Threshold(object):
308 class threshold(object):
309 '''Class that indicates whether a file descriptor has reached a
309 '''Class that indicates whether a file descriptor has reached a
310 threshold of readable bytes available.
310 threshold of readable bytes available.
311
311
312 This class is not thread-safe.'''
312 This class is not thread-safe.'''
313
313
314 __slots__ = (
314 __slots__ = (
315 'fd',
315 'fd',
316 'threshold',
316 'threshold',
317 '_iocbuf',
317 '_iocbuf',
318 )
318 )
319
319
320 def __init__(self, fd, threshold=1024):
320 def __init__(self, fd, threshold=1024):
321 self.fd = fd
321 self.fd = fd
322 self.threshold = threshold
322 self.threshold = threshold
323 self._iocbuf = array.array('i', [0])
323 self._iocbuf = array.array('i', [0])
324
324
325 def readable(self):
325 def readable(self):
326 '''Return the number of bytes readable on this file descriptor.'''
326 '''Return the number of bytes readable on this file descriptor.'''
327
327
328 fcntl.ioctl(self.fd, termios.FIONREAD, self._iocbuf, True)
328 fcntl.ioctl(self.fd, termios.FIONREAD, self._iocbuf, True)
329 return self._iocbuf[0]
329 return self._iocbuf[0]
330
330
331 def __call__(self):
331 def __call__(self):
332 '''Indicate whether the number of readable bytes has met or
332 '''Indicate whether the number of readable bytes has met or
333 exceeded the threshold.'''
333 exceeded the threshold.'''
334
334
335 return self.readable() >= self.threshold
335 return self.readable() >= self.threshold
@@ -1,757 +1,757 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, count):
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 RepoWatcher(object):
116 class repowatcher(object):
117 poll_events = select.POLLIN
117 poll_events = select.POLLIN
118 statuskeys = 'almr!?'
118 statuskeys = 'almr!?'
119 mask = (
119 mask = (
120 inotify.IN_ATTRIB |
120 inotify.IN_ATTRIB |
121 inotify.IN_CREATE |
121 inotify.IN_CREATE |
122 inotify.IN_DELETE |
122 inotify.IN_DELETE |
123 inotify.IN_DELETE_SELF |
123 inotify.IN_DELETE_SELF |
124 inotify.IN_MODIFY |
124 inotify.IN_MODIFY |
125 inotify.IN_MOVED_FROM |
125 inotify.IN_MOVED_FROM |
126 inotify.IN_MOVED_TO |
126 inotify.IN_MOVED_TO |
127 inotify.IN_MOVE_SELF |
127 inotify.IN_MOVE_SELF |
128 inotify.IN_ONLYDIR |
128 inotify.IN_ONLYDIR |
129 inotify.IN_UNMOUNT |
129 inotify.IN_UNMOUNT |
130 0)
130 0)
131
131
132 def __init__(self, ui, repo, master):
132 def __init__(self, ui, repo, master):
133 self.ui = ui
133 self.ui = ui
134 self.repo = repo
134 self.repo = repo
135 self.wprefix = self.repo.wjoin('')
135 self.wprefix = self.repo.wjoin('')
136 self.timeout = None
136 self.timeout = None
137 self.master = master
137 self.master = master
138 try:
138 try:
139 self.watcher = watcher.Watcher()
139 self.watcher = watcher.watcher()
140 except OSError, err:
140 except OSError, err:
141 raise util.Abort(_('inotify service not available: %s') %
141 raise util.Abort(_('inotify service not available: %s') %
142 err.strerror)
142 err.strerror)
143 self.threshold = watcher.Threshold(self.watcher)
143 self.threshold = watcher.threshold(self.watcher)
144 self.registered = True
144 self.registered = True
145 self.fileno = self.watcher.fileno
145 self.fileno = self.watcher.fileno
146
146
147 self.repo.dirstate.__class__.inotifyserver = True
147 self.repo.dirstate.__class__.inotifyserver = True
148
148
149 self.tree = {}
149 self.tree = {}
150 self.statcache = {}
150 self.statcache = {}
151 self.statustrees = dict([(s, {}) for s in self.statuskeys])
151 self.statustrees = dict([(s, {}) for s in self.statuskeys])
152
152
153 self.watches = 0
153 self.watches = 0
154 self.last_event = None
154 self.last_event = None
155
155
156 self.eventq = {}
156 self.eventq = {}
157 self.deferred = 0
157 self.deferred = 0
158
158
159 self.ds_info = self.dirstate_info()
159 self.ds_info = self.dirstate_info()
160 self.scan()
160 self.scan()
161
161
162 def event_time(self):
162 def event_time(self):
163 last = self.last_event
163 last = self.last_event
164 now = time.time()
164 now = time.time()
165 self.last_event = now
165 self.last_event = now
166
166
167 if last is None:
167 if last is None:
168 return 'start'
168 return 'start'
169 delta = now - last
169 delta = now - last
170 if delta < 5:
170 if delta < 5:
171 return '+%.3f' % delta
171 return '+%.3f' % delta
172 if delta < 50:
172 if delta < 50:
173 return '+%.2f' % delta
173 return '+%.2f' % delta
174 return '+%.1f' % delta
174 return '+%.1f' % delta
175
175
176 def dirstate_info(self):
176 def dirstate_info(self):
177 try:
177 try:
178 st = os.lstat(self.repo.join('dirstate'))
178 st = os.lstat(self.repo.join('dirstate'))
179 return st.st_mtime, st.st_ino
179 return st.st_mtime, st.st_ino
180 except OSError, err:
180 except OSError, err:
181 if err.errno != errno.ENOENT:
181 if err.errno != errno.ENOENT:
182 raise
182 raise
183 return 0, 0
183 return 0, 0
184
184
185 def add_watch(self, path, mask):
185 def add_watch(self, path, mask):
186 if not path:
186 if not path:
187 return
187 return
188 if self.watcher.path(path) is None:
188 if self.watcher.path(path) is None:
189 if self.ui.debugflag:
189 if self.ui.debugflag:
190 self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
190 self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
191 try:
191 try:
192 self.watcher.add(path, mask)
192 self.watcher.add(path, mask)
193 self.watches += 1
193 self.watches += 1
194 except OSError, err:
194 except OSError, err:
195 if err.errno in (errno.ENOENT, errno.ENOTDIR):
195 if err.errno in (errno.ENOENT, errno.ENOTDIR):
196 return
196 return
197 if err.errno != errno.ENOSPC:
197 if err.errno != errno.ENOSPC:
198 raise
198 raise
199 _explain_watch_limit(self.ui, self.repo, self.watches)
199 _explain_watch_limit(self.ui, self.repo, self.watches)
200
200
201 def setup(self):
201 def setup(self):
202 self.ui.note(_('watching directories under %r\n') % self.repo.root)
202 self.ui.note(_('watching directories under %r\n') % self.repo.root)
203 self.add_watch(self.repo.path, inotify.IN_DELETE)
203 self.add_watch(self.repo.path, inotify.IN_DELETE)
204 self.check_dirstate()
204 self.check_dirstate()
205
205
206 def wpath(self, evt):
206 def wpath(self, evt):
207 path = evt.fullpath
207 path = evt.fullpath
208 if path == self.repo.root:
208 if path == self.repo.root:
209 return ''
209 return ''
210 if path.startswith(self.wprefix):
210 if path.startswith(self.wprefix):
211 return path[len(self.wprefix):]
211 return path[len(self.wprefix):]
212 raise 'wtf? ' + path
212 raise 'wtf? ' + path
213
213
214 def dir(self, tree, path):
214 def dir(self, tree, path):
215 if path:
215 if path:
216 for name in path.split('/'):
216 for name in path.split('/'):
217 tree = tree.setdefault(name, {})
217 tree = tree.setdefault(name, {})
218 return tree
218 return tree
219
219
220 def lookup(self, path, tree):
220 def lookup(self, path, tree):
221 if path:
221 if path:
222 try:
222 try:
223 for name in path.split('/'):
223 for name in path.split('/'):
224 tree = tree[name]
224 tree = tree[name]
225 except KeyError:
225 except KeyError:
226 return 'x'
226 return 'x'
227 except TypeError:
227 except TypeError:
228 return 'd'
228 return 'd'
229 return tree
229 return tree
230
230
231 def split(self, path):
231 def split(self, path):
232 c = path.rfind('/')
232 c = path.rfind('/')
233 if c == -1:
233 if c == -1:
234 return '', path
234 return '', path
235 return path[:c], path[c+1:]
235 return path[:c], path[c+1:]
236
236
237 def filestatus(self, fn, st):
237 def filestatus(self, fn, st):
238 try:
238 try:
239 type_, mode, size, time = self.repo.dirstate._map[fn][:4]
239 type_, mode, size, time = self.repo.dirstate._map[fn][:4]
240 except KeyError:
240 except KeyError:
241 type_ = '?'
241 type_ = '?'
242 if type_ == 'n':
242 if type_ == 'n':
243 st_mode, st_size, st_mtime = st
243 st_mode, st_size, st_mtime = st
244 if size == -1:
244 if size == -1:
245 return 'l'
245 return 'l'
246 if size and (size != st_size or (mode ^ st_mode) & 0100):
246 if size and (size != st_size or (mode ^ st_mode) & 0100):
247 return 'm'
247 return 'm'
248 if time != int(st_mtime):
248 if time != int(st_mtime):
249 return 'l'
249 return 'l'
250 return 'n'
250 return 'n'
251 if type_ == '?' and self.repo.dirstate._ignore(fn):
251 if type_ == '?' and self.repo.dirstate._ignore(fn):
252 return 'i'
252 return 'i'
253 return type_
253 return type_
254
254
255 def updatestatus(self, wfn, osstat=None, newstatus=None):
255 def updatestatus(self, wfn, osstat=None, newstatus=None):
256 '''
256 '''
257 Update the stored status of a file or directory.
257 Update the stored status of a file or directory.
258
258
259 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
259 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
260
260
261 newstatus: char in statuskeys, new status to apply.
261 newstatus: char in statuskeys, new status to apply.
262 '''
262 '''
263 if osstat:
263 if osstat:
264 newstatus = self.filestatus(wfn, osstat)
264 newstatus = self.filestatus(wfn, osstat)
265 else:
265 else:
266 self.statcache.pop(wfn, None)
266 self.statcache.pop(wfn, None)
267 root, fn = self.split(wfn)
267 root, fn = self.split(wfn)
268 d = self.dir(self.tree, root)
268 d = self.dir(self.tree, root)
269 oldstatus = d.get(fn)
269 oldstatus = d.get(fn)
270 isdir = False
270 isdir = False
271 if oldstatus:
271 if oldstatus:
272 try:
272 try:
273 if not newstatus:
273 if not newstatus:
274 if oldstatus in 'almn':
274 if oldstatus in 'almn':
275 newstatus = '!'
275 newstatus = '!'
276 elif oldstatus == 'r':
276 elif oldstatus == 'r':
277 newstatus = 'r'
277 newstatus = 'r'
278 except TypeError:
278 except TypeError:
279 # oldstatus may be a dict left behind by a deleted
279 # oldstatus may be a dict left behind by a deleted
280 # directory
280 # directory
281 isdir = True
281 isdir = True
282 else:
282 else:
283 if oldstatus in self.statuskeys and oldstatus != newstatus:
283 if oldstatus in self.statuskeys and oldstatus != newstatus:
284 del self.dir(self.statustrees[oldstatus], root)[fn]
284 del self.dir(self.statustrees[oldstatus], root)[fn]
285 if self.ui.debugflag and oldstatus != newstatus:
285 if self.ui.debugflag and oldstatus != newstatus:
286 if isdir:
286 if isdir:
287 self.ui.note(_('status: %r dir(%d) -> %s\n') %
287 self.ui.note(_('status: %r dir(%d) -> %s\n') %
288 (wfn, len(oldstatus), newstatus))
288 (wfn, len(oldstatus), newstatus))
289 else:
289 else:
290 self.ui.note(_('status: %r %s -> %s\n') %
290 self.ui.note(_('status: %r %s -> %s\n') %
291 (wfn, oldstatus, newstatus))
291 (wfn, oldstatus, newstatus))
292 if not isdir:
292 if not isdir:
293 if newstatus and newstatus != 'i':
293 if newstatus and newstatus != 'i':
294 d[fn] = newstatus
294 d[fn] = newstatus
295 if newstatus in self.statuskeys:
295 if newstatus in self.statuskeys:
296 dd = self.dir(self.statustrees[newstatus], root)
296 dd = self.dir(self.statustrees[newstatus], root)
297 if oldstatus != newstatus or fn not in dd:
297 if oldstatus != newstatus or fn not in dd:
298 dd[fn] = newstatus
298 dd[fn] = newstatus
299 else:
299 else:
300 d.pop(fn, None)
300 d.pop(fn, None)
301 elif not newstatus:
301 elif not newstatus:
302 # a directory is being removed, check its contents
302 # a directory is being removed, check its contents
303 for subfile, b in oldstatus.copy().iteritems():
303 for subfile, b in oldstatus.copy().iteritems():
304 self.updatestatus(wfn + '/' + subfile, None)
304 self.updatestatus(wfn + '/' + subfile, None)
305
305
306
306
307 def check_deleted(self, key):
307 def check_deleted(self, key):
308 # Files that had been deleted but were present in the dirstate
308 # Files that had been deleted but were present in the dirstate
309 # may have vanished from the dirstate; we must clean them up.
309 # may have vanished from the dirstate; we must clean them up.
310 nuke = []
310 nuke = []
311 for wfn, ignore in self.walk(key, self.statustrees[key]):
311 for wfn, ignore in self.walk(key, self.statustrees[key]):
312 if wfn not in self.repo.dirstate:
312 if wfn not in self.repo.dirstate:
313 nuke.append(wfn)
313 nuke.append(wfn)
314 for wfn in nuke:
314 for wfn in nuke:
315 root, fn = self.split(wfn)
315 root, fn = self.split(wfn)
316 del self.dir(self.statustrees[key], root)[fn]
316 del self.dir(self.statustrees[key], root)[fn]
317 del self.dir(self.tree, root)[fn]
317 del self.dir(self.tree, root)[fn]
318
318
319 def scan(self, topdir=''):
319 def scan(self, topdir=''):
320 self.handle_timeout()
320 self.handle_timeout()
321 ds = self.repo.dirstate._map.copy()
321 ds = self.repo.dirstate._map.copy()
322 self.add_watch(join(self.repo.root, topdir), self.mask)
322 self.add_watch(join(self.repo.root, topdir), self.mask)
323 for root, dirs, files in walk(self.repo, topdir):
323 for root, dirs, files in walk(self.repo, topdir):
324 for d in dirs:
324 for d in dirs:
325 self.add_watch(join(root, d), self.mask)
325 self.add_watch(join(root, d), self.mask)
326 wroot = root[len(self.wprefix):]
326 wroot = root[len(self.wprefix):]
327 d = self.dir(self.tree, wroot)
327 d = self.dir(self.tree, wroot)
328 for fn in files:
328 for fn in files:
329 wfn = join(wroot, fn)
329 wfn = join(wroot, fn)
330 self.updatestatus(wfn, self.getstat(wfn))
330 self.updatestatus(wfn, self.getstat(wfn))
331 ds.pop(wfn, None)
331 ds.pop(wfn, None)
332 wtopdir = topdir
332 wtopdir = topdir
333 if wtopdir and wtopdir[-1] != '/':
333 if wtopdir and wtopdir[-1] != '/':
334 wtopdir += '/'
334 wtopdir += '/'
335 for wfn, state in ds.iteritems():
335 for wfn, state in ds.iteritems():
336 if not wfn.startswith(wtopdir):
336 if not wfn.startswith(wtopdir):
337 continue
337 continue
338 try:
338 try:
339 st = self.stat(wfn)
339 st = self.stat(wfn)
340 except OSError:
340 except OSError:
341 status = state[0]
341 status = state[0]
342 self.updatestatus(wfn, None, newstatus=status)
342 self.updatestatus(wfn, None, newstatus=status)
343 else:
343 else:
344 self.updatestatus(wfn, st)
344 self.updatestatus(wfn, st)
345 self.check_deleted('!')
345 self.check_deleted('!')
346 self.check_deleted('r')
346 self.check_deleted('r')
347
347
348 def check_dirstate(self):
348 def check_dirstate(self):
349 ds_info = self.dirstate_info()
349 ds_info = self.dirstate_info()
350 if ds_info == self.ds_info:
350 if ds_info == self.ds_info:
351 return
351 return
352 self.ds_info = ds_info
352 self.ds_info = ds_info
353 if not self.ui.debugflag:
353 if not self.ui.debugflag:
354 self.last_event = None
354 self.last_event = None
355 self.ui.note(_('%s dirstate reload\n') % self.event_time())
355 self.ui.note(_('%s dirstate reload\n') % self.event_time())
356 self.repo.dirstate.invalidate()
356 self.repo.dirstate.invalidate()
357 self.scan()
357 self.scan()
358 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
358 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
359
359
360 def walk(self, states, tree, prefix=''):
360 def walk(self, states, tree, prefix=''):
361 # This is the "inner loop" when talking to the client.
361 # This is the "inner loop" when talking to the client.
362
362
363 for name, val in tree.iteritems():
363 for name, val in tree.iteritems():
364 path = join(prefix, name)
364 path = join(prefix, name)
365 try:
365 try:
366 if val in states:
366 if val in states:
367 yield path, val
367 yield path, val
368 except TypeError:
368 except TypeError:
369 for p in self.walk(states, val, path):
369 for p in self.walk(states, val, path):
370 yield p
370 yield p
371
371
372 def update_hgignore(self):
372 def update_hgignore(self):
373 # An update of the ignore file can potentially change the
373 # An update of the ignore file can potentially change the
374 # states of all unknown and ignored files.
374 # states of all unknown and ignored files.
375
375
376 # XXX If the user has other ignore files outside the repo, or
376 # XXX If the user has other ignore files outside the repo, or
377 # changes their list of ignore files at run time, we'll
377 # changes their list of ignore files at run time, we'll
378 # potentially never see changes to them. We could get the
378 # potentially never see changes to them. We could get the
379 # client to report to us what ignore data they're using.
379 # client to report to us what ignore data they're using.
380 # But it's easier to do nothing than to open that can of
380 # But it's easier to do nothing than to open that can of
381 # worms.
381 # worms.
382
382
383 if '_ignore' in self.repo.dirstate.__dict__:
383 if '_ignore' in self.repo.dirstate.__dict__:
384 delattr(self.repo.dirstate, '_ignore')
384 delattr(self.repo.dirstate, '_ignore')
385 self.ui.note(_('rescanning due to .hgignore change\n'))
385 self.ui.note(_('rescanning due to .hgignore change\n'))
386 self.scan()
386 self.scan()
387
387
388 def getstat(self, wpath):
388 def getstat(self, wpath):
389 try:
389 try:
390 return self.statcache[wpath]
390 return self.statcache[wpath]
391 except KeyError:
391 except KeyError:
392 try:
392 try:
393 return self.stat(wpath)
393 return self.stat(wpath)
394 except OSError, err:
394 except OSError, err:
395 if err.errno != errno.ENOENT:
395 if err.errno != errno.ENOENT:
396 raise
396 raise
397
397
398 def stat(self, wpath):
398 def stat(self, wpath):
399 try:
399 try:
400 st = os.lstat(join(self.wprefix, wpath))
400 st = os.lstat(join(self.wprefix, wpath))
401 ret = st.st_mode, st.st_size, st.st_mtime
401 ret = st.st_mode, st.st_size, st.st_mtime
402 self.statcache[wpath] = ret
402 self.statcache[wpath] = ret
403 return ret
403 return ret
404 except OSError:
404 except OSError:
405 self.statcache.pop(wpath, None)
405 self.statcache.pop(wpath, None)
406 raise
406 raise
407
407
408 def created(self, wpath):
408 def created(self, wpath):
409 if wpath == '.hgignore':
409 if wpath == '.hgignore':
410 self.update_hgignore()
410 self.update_hgignore()
411 try:
411 try:
412 st = self.stat(wpath)
412 st = self.stat(wpath)
413 if stat.S_ISREG(st[0]):
413 if stat.S_ISREG(st[0]):
414 self.updatestatus(wpath, st)
414 self.updatestatus(wpath, st)
415 except OSError:
415 except OSError:
416 pass
416 pass
417
417
418 def modified(self, wpath):
418 def modified(self, wpath):
419 if wpath == '.hgignore':
419 if wpath == '.hgignore':
420 self.update_hgignore()
420 self.update_hgignore()
421 try:
421 try:
422 st = self.stat(wpath)
422 st = self.stat(wpath)
423 if stat.S_ISREG(st[0]):
423 if stat.S_ISREG(st[0]):
424 if self.repo.dirstate[wpath] in 'lmn':
424 if self.repo.dirstate[wpath] in 'lmn':
425 self.updatestatus(wpath, st)
425 self.updatestatus(wpath, st)
426 except OSError:
426 except OSError:
427 pass
427 pass
428
428
429 def deleted(self, wpath):
429 def deleted(self, wpath):
430 if wpath == '.hgignore':
430 if wpath == '.hgignore':
431 self.update_hgignore()
431 self.update_hgignore()
432 elif wpath.startswith('.hg/'):
432 elif wpath.startswith('.hg/'):
433 if wpath == '.hg/wlock':
433 if wpath == '.hg/wlock':
434 self.check_dirstate()
434 self.check_dirstate()
435 return
435 return
436
436
437 self.updatestatus(wpath, None)
437 self.updatestatus(wpath, None)
438
438
439 def schedule_work(self, wpath, evt):
439 def schedule_work(self, wpath, evt):
440 prev = self.eventq.setdefault(wpath, [])
440 prev = self.eventq.setdefault(wpath, [])
441 try:
441 try:
442 if prev and evt == 'm' and prev[-1] in 'cm':
442 if prev and evt == 'm' and prev[-1] in 'cm':
443 return
443 return
444 self.eventq[wpath].append(evt)
444 self.eventq[wpath].append(evt)
445 finally:
445 finally:
446 self.deferred += 1
446 self.deferred += 1
447 self.timeout = 250
447 self.timeout = 250
448
448
449 def deferred_event(self, wpath, evt):
449 def deferred_event(self, wpath, evt):
450 if evt == 'c':
450 if evt == 'c':
451 self.created(wpath)
451 self.created(wpath)
452 elif evt == 'm':
452 elif evt == 'm':
453 self.modified(wpath)
453 self.modified(wpath)
454 elif evt == 'd':
454 elif evt == 'd':
455 self.deleted(wpath)
455 self.deleted(wpath)
456
456
457 def process_create(self, wpath, evt):
457 def process_create(self, wpath, evt):
458 if self.ui.debugflag:
458 if self.ui.debugflag:
459 self.ui.note(_('%s event: created %s\n') %
459 self.ui.note(_('%s event: created %s\n') %
460 (self.event_time(), wpath))
460 (self.event_time(), wpath))
461
461
462 if evt.mask & inotify.IN_ISDIR:
462 if evt.mask & inotify.IN_ISDIR:
463 self.scan(wpath)
463 self.scan(wpath)
464 else:
464 else:
465 self.schedule_work(wpath, 'c')
465 self.schedule_work(wpath, 'c')
466
466
467 def process_delete(self, wpath, evt):
467 def process_delete(self, wpath, evt):
468 if self.ui.debugflag:
468 if self.ui.debugflag:
469 self.ui.note(_('%s event: deleted %s\n') %
469 self.ui.note(_('%s event: deleted %s\n') %
470 (self.event_time(), wpath))
470 (self.event_time(), wpath))
471
471
472 if evt.mask & inotify.IN_ISDIR:
472 if evt.mask & inotify.IN_ISDIR:
473 self.scan(wpath)
473 self.scan(wpath)
474 self.schedule_work(wpath, 'd')
474 self.schedule_work(wpath, 'd')
475
475
476 def process_modify(self, wpath, evt):
476 def process_modify(self, wpath, evt):
477 if self.ui.debugflag:
477 if self.ui.debugflag:
478 self.ui.note(_('%s event: modified %s\n') %
478 self.ui.note(_('%s event: modified %s\n') %
479 (self.event_time(), wpath))
479 (self.event_time(), wpath))
480
480
481 if not (evt.mask & inotify.IN_ISDIR):
481 if not (evt.mask & inotify.IN_ISDIR):
482 self.schedule_work(wpath, 'm')
482 self.schedule_work(wpath, 'm')
483
483
484 def process_unmount(self, evt):
484 def process_unmount(self, evt):
485 self.ui.warn(_('filesystem containing %s was unmounted\n') %
485 self.ui.warn(_('filesystem containing %s was unmounted\n') %
486 evt.fullpath)
486 evt.fullpath)
487 sys.exit(0)
487 sys.exit(0)
488
488
489 def handle_event(self, fd, event):
489 def handle_event(self, fd, event):
490 if self.ui.debugflag:
490 if self.ui.debugflag:
491 self.ui.note(_('%s readable: %d bytes\n') %
491 self.ui.note(_('%s readable: %d bytes\n') %
492 (self.event_time(), self.threshold.readable()))
492 (self.event_time(), self.threshold.readable()))
493 if not self.threshold():
493 if not self.threshold():
494 if self.registered:
494 if self.registered:
495 if self.ui.debugflag:
495 if self.ui.debugflag:
496 self.ui.note(_('%s below threshold - unhooking\n') %
496 self.ui.note(_('%s below threshold - unhooking\n') %
497 (self.event_time()))
497 (self.event_time()))
498 self.master.poll.unregister(fd)
498 self.master.poll.unregister(fd)
499 self.registered = False
499 self.registered = False
500 self.timeout = 250
500 self.timeout = 250
501 else:
501 else:
502 self.read_events()
502 self.read_events()
503
503
504 def read_events(self, bufsize=None):
504 def read_events(self, bufsize=None):
505 events = self.watcher.read(bufsize)
505 events = self.watcher.read(bufsize)
506 if self.ui.debugflag:
506 if self.ui.debugflag:
507 self.ui.note(_('%s reading %d events\n') %
507 self.ui.note(_('%s reading %d events\n') %
508 (self.event_time(), len(events)))
508 (self.event_time(), len(events)))
509 for evt in events:
509 for evt in events:
510 wpath = self.wpath(evt)
510 wpath = self.wpath(evt)
511 if evt.mask & inotify.IN_UNMOUNT:
511 if evt.mask & inotify.IN_UNMOUNT:
512 self.process_unmount(wpath, evt)
512 self.process_unmount(wpath, evt)
513 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
513 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
514 self.process_modify(wpath, evt)
514 self.process_modify(wpath, evt)
515 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
515 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
516 inotify.IN_MOVED_FROM):
516 inotify.IN_MOVED_FROM):
517 self.process_delete(wpath, evt)
517 self.process_delete(wpath, evt)
518 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
518 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
519 self.process_create(wpath, evt)
519 self.process_create(wpath, evt)
520
520
521 def handle_timeout(self):
521 def handle_timeout(self):
522 if not self.registered:
522 if not self.registered:
523 if self.ui.debugflag:
523 if self.ui.debugflag:
524 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
524 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
525 (self.event_time(), self.threshold.readable()))
525 (self.event_time(), self.threshold.readable()))
526 self.read_events(0)
526 self.read_events(0)
527 self.master.poll.register(self, select.POLLIN)
527 self.master.poll.register(self, select.POLLIN)
528 self.registered = True
528 self.registered = True
529
529
530 if self.eventq:
530 if self.eventq:
531 if self.ui.debugflag:
531 if self.ui.debugflag:
532 self.ui.note(_('%s processing %d deferred events as %d\n') %
532 self.ui.note(_('%s processing %d deferred events as %d\n') %
533 (self.event_time(), self.deferred,
533 (self.event_time(), self.deferred,
534 len(self.eventq)))
534 len(self.eventq)))
535 for wpath, evts in sorted(self.eventq.iteritems()):
535 for wpath, evts in sorted(self.eventq.iteritems()):
536 for evt in evts:
536 for evt in evts:
537 self.deferred_event(wpath, evt)
537 self.deferred_event(wpath, evt)
538 self.eventq.clear()
538 self.eventq.clear()
539 self.deferred = 0
539 self.deferred = 0
540 self.timeout = None
540 self.timeout = None
541
541
542 def shutdown(self):
542 def shutdown(self):
543 self.watcher.close()
543 self.watcher.close()
544
544
545 class Server(object):
545 class server(object):
546 poll_events = select.POLLIN
546 poll_events = select.POLLIN
547
547
548 def __init__(self, ui, repo, repowatcher, timeout):
548 def __init__(self, ui, repo, repowatcher, timeout):
549 self.ui = ui
549 self.ui = ui
550 self.repo = repo
550 self.repo = repo
551 self.repowatcher = repowatcher
551 self.repowatcher = repowatcher
552 self.timeout = timeout
552 self.timeout = timeout
553 self.sock = socket.socket(socket.AF_UNIX)
553 self.sock = socket.socket(socket.AF_UNIX)
554 self.sockpath = self.repo.join('inotify.sock')
554 self.sockpath = self.repo.join('inotify.sock')
555 self.realsockpath = None
555 self.realsockpath = None
556 try:
556 try:
557 self.sock.bind(self.sockpath)
557 self.sock.bind(self.sockpath)
558 except socket.error, err:
558 except socket.error, err:
559 if err[0] == errno.EADDRINUSE:
559 if err[0] == errno.EADDRINUSE:
560 raise AlreadyStartedException(_('could not start server: %s')
560 raise AlreadyStartedException(_('could not start server: %s')
561 % err[1])
561 % err[1])
562 if err[0] == "AF_UNIX path too long":
562 if err[0] == "AF_UNIX path too long":
563 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
563 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
564 self.realsockpath = os.path.join(tempdir, "inotify.sock")
564 self.realsockpath = os.path.join(tempdir, "inotify.sock")
565 try:
565 try:
566 self.sock.bind(self.realsockpath)
566 self.sock.bind(self.realsockpath)
567 os.symlink(self.realsockpath, self.sockpath)
567 os.symlink(self.realsockpath, self.sockpath)
568 except (OSError, socket.error), inst:
568 except (OSError, socket.error), inst:
569 try:
569 try:
570 os.unlink(self.realsockpath)
570 os.unlink(self.realsockpath)
571 except:
571 except:
572 pass
572 pass
573 os.rmdir(tempdir)
573 os.rmdir(tempdir)
574 if inst.errno == errno.EEXIST:
574 if inst.errno == errno.EEXIST:
575 raise AlreadyStartedException(_('could not start server: %s')
575 raise AlreadyStartedException(_('could not start server: %s')
576 % inst.strerror)
576 % inst.strerror)
577 raise
577 raise
578 else:
578 else:
579 raise
579 raise
580 self.sock.listen(5)
580 self.sock.listen(5)
581 self.fileno = self.sock.fileno
581 self.fileno = self.sock.fileno
582
582
583 def handle_timeout(self):
583 def handle_timeout(self):
584 pass
584 pass
585
585
586 def handle_event(self, fd, event):
586 def handle_event(self, fd, event):
587 sock, addr = self.sock.accept()
587 sock, addr = self.sock.accept()
588
588
589 cs = common.recvcs(sock)
589 cs = common.recvcs(sock)
590 version = ord(cs.read(1))
590 version = ord(cs.read(1))
591
591
592 sock.sendall(chr(common.version))
592 sock.sendall(chr(common.version))
593
593
594 if version != common.version:
594 if version != common.version:
595 self.ui.warn(_('received query from incompatible client '
595 self.ui.warn(_('received query from incompatible client '
596 'version %d\n') % version)
596 'version %d\n') % version)
597 return
597 return
598
598
599 names = cs.read().split('\0')
599 names = cs.read().split('\0')
600
600
601 states = names.pop()
601 states = names.pop()
602
602
603 self.ui.note(_('answering query for %r\n') % states)
603 self.ui.note(_('answering query for %r\n') % states)
604
604
605 if self.repowatcher.timeout:
605 if self.repowatcher.timeout:
606 # We got a query while a rescan is pending. Make sure we
606 # We got a query while a rescan is pending. Make sure we
607 # rescan before responding, or we could give back a wrong
607 # rescan before responding, or we could give back a wrong
608 # answer.
608 # answer.
609 self.repowatcher.handle_timeout()
609 self.repowatcher.handle_timeout()
610
610
611 if not names:
611 if not names:
612 def genresult(states, tree):
612 def genresult(states, tree):
613 for fn, state in self.repowatcher.walk(states, tree):
613 for fn, state in self.repowatcher.walk(states, tree):
614 yield fn
614 yield fn
615 else:
615 else:
616 def genresult(states, tree):
616 def genresult(states, tree):
617 for fn in names:
617 for fn in names:
618 l = self.repowatcher.lookup(fn, tree)
618 l = self.repowatcher.lookup(fn, tree)
619 try:
619 try:
620 if l in states:
620 if l in states:
621 yield fn
621 yield fn
622 except TypeError:
622 except TypeError:
623 for f, s in self.repowatcher.walk(states, l, fn):
623 for f, s in self.repowatcher.walk(states, l, fn):
624 yield f
624 yield f
625
625
626 results = ['\0'.join(r) for r in [
626 results = ['\0'.join(r) for r in [
627 genresult('l', self.repowatcher.statustrees['l']),
627 genresult('l', self.repowatcher.statustrees['l']),
628 genresult('m', self.repowatcher.statustrees['m']),
628 genresult('m', self.repowatcher.statustrees['m']),
629 genresult('a', self.repowatcher.statustrees['a']),
629 genresult('a', self.repowatcher.statustrees['a']),
630 genresult('r', self.repowatcher.statustrees['r']),
630 genresult('r', self.repowatcher.statustrees['r']),
631 genresult('!', self.repowatcher.statustrees['!']),
631 genresult('!', self.repowatcher.statustrees['!']),
632 '?' in states
632 '?' in states
633 and genresult('?', self.repowatcher.statustrees['?'])
633 and genresult('?', self.repowatcher.statustrees['?'])
634 or [],
634 or [],
635 [],
635 [],
636 'c' in states and genresult('n', self.repowatcher.tree) or [],
636 'c' in states and genresult('n', self.repowatcher.tree) or [],
637 ]]
637 ]]
638
638
639 try:
639 try:
640 try:
640 try:
641 sock.sendall(struct.pack(common.resphdrfmt,
641 sock.sendall(struct.pack(common.resphdrfmt,
642 *map(len, results)))
642 *map(len, results)))
643 sock.sendall(''.join(results))
643 sock.sendall(''.join(results))
644 finally:
644 finally:
645 sock.shutdown(socket.SHUT_WR)
645 sock.shutdown(socket.SHUT_WR)
646 except socket.error, err:
646 except socket.error, err:
647 if err[0] != errno.EPIPE:
647 if err[0] != errno.EPIPE:
648 raise
648 raise
649
649
650 def shutdown(self):
650 def shutdown(self):
651 self.sock.close()
651 self.sock.close()
652 try:
652 try:
653 os.unlink(self.sockpath)
653 os.unlink(self.sockpath)
654 if self.realsockpath:
654 if self.realsockpath:
655 os.unlink(self.realsockpath)
655 os.unlink(self.realsockpath)
656 os.rmdir(os.path.dirname(self.realsockpath))
656 os.rmdir(os.path.dirname(self.realsockpath))
657 except OSError, err:
657 except OSError, err:
658 if err.errno != errno.ENOENT:
658 if err.errno != errno.ENOENT:
659 raise
659 raise
660
660
661 class Master(object):
661 class master(object):
662 def __init__(self, ui, repo, timeout=None):
662 def __init__(self, ui, repo, timeout=None):
663 self.ui = ui
663 self.ui = ui
664 self.repo = repo
664 self.repo = repo
665 self.poll = select.poll()
665 self.poll = select.poll()
666 self.repowatcher = RepoWatcher(ui, repo, self)
666 self.repowatcher = repowatcher(ui, repo, self)
667 self.server = Server(ui, repo, self.repowatcher, timeout)
667 self.server = server(ui, repo, self.repowatcher, timeout)
668 self.table = {}
668 self.table = {}
669 for obj in (self.repowatcher, self.server):
669 for obj in (self.repowatcher, self.server):
670 fd = obj.fileno()
670 fd = obj.fileno()
671 self.table[fd] = obj
671 self.table[fd] = obj
672 self.poll.register(fd, obj.poll_events)
672 self.poll.register(fd, obj.poll_events)
673
673
674 def register(self, fd, mask):
674 def register(self, fd, mask):
675 self.poll.register(fd, mask)
675 self.poll.register(fd, mask)
676
676
677 def shutdown(self):
677 def shutdown(self):
678 for obj in self.table.itervalues():
678 for obj in self.table.itervalues():
679 obj.shutdown()
679 obj.shutdown()
680
680
681 def run(self):
681 def run(self):
682 self.repowatcher.setup()
682 self.repowatcher.setup()
683 self.ui.note(_('finished setup\n'))
683 self.ui.note(_('finished setup\n'))
684 if os.getenv('TIME_STARTUP'):
684 if os.getenv('TIME_STARTUP'):
685 sys.exit(0)
685 sys.exit(0)
686 while True:
686 while True:
687 timeout = None
687 timeout = None
688 timeobj = None
688 timeobj = None
689 for obj in self.table.itervalues():
689 for obj in self.table.itervalues():
690 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
690 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
691 timeout, timeobj = obj.timeout, obj
691 timeout, timeobj = obj.timeout, obj
692 try:
692 try:
693 if self.ui.debugflag:
693 if self.ui.debugflag:
694 if timeout is None:
694 if timeout is None:
695 self.ui.note(_('polling: no timeout\n'))
695 self.ui.note(_('polling: no timeout\n'))
696 else:
696 else:
697 self.ui.note(_('polling: %sms timeout\n') % timeout)
697 self.ui.note(_('polling: %sms timeout\n') % timeout)
698 events = self.poll.poll(timeout)
698 events = self.poll.poll(timeout)
699 except select.error, err:
699 except select.error, err:
700 if err[0] == errno.EINTR:
700 if err[0] == errno.EINTR:
701 continue
701 continue
702 raise
702 raise
703 if events:
703 if events:
704 for fd, event in events:
704 for fd, event in events:
705 self.table[fd].handle_event(fd, event)
705 self.table[fd].handle_event(fd, event)
706 elif timeobj:
706 elif timeobj:
707 timeobj.handle_timeout()
707 timeobj.handle_timeout()
708
708
709 def start(ui, repo):
709 def start(ui, repo):
710 def closefds(ignore):
710 def closefds(ignore):
711 # (from python bug #1177468)
711 # (from python bug #1177468)
712 # close all inherited file descriptors
712 # close all inherited file descriptors
713 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
713 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
714 # a file descriptor is kept internally as os._urandomfd (created on demand
714 # a file descriptor is kept internally as os._urandomfd (created on demand
715 # the first time os.urandom() is called), and should not be closed
715 # the first time os.urandom() is called), and should not be closed
716 try:
716 try:
717 os.urandom(4)
717 os.urandom(4)
718 urandom_fd = getattr(os, '_urandomfd', None)
718 urandom_fd = getattr(os, '_urandomfd', None)
719 except AttributeError:
719 except AttributeError:
720 urandom_fd = None
720 urandom_fd = None
721 ignore.append(urandom_fd)
721 ignore.append(urandom_fd)
722 for fd in range(3, 256):
722 for fd in range(3, 256):
723 if fd in ignore:
723 if fd in ignore:
724 continue
724 continue
725 try:
725 try:
726 os.close(fd)
726 os.close(fd)
727 except OSError:
727 except OSError:
728 pass
728 pass
729
729
730 m = Master(ui, repo)
730 m = master(ui, repo)
731 sys.stdout.flush()
731 sys.stdout.flush()
732 sys.stderr.flush()
732 sys.stderr.flush()
733
733
734 pid = os.fork()
734 pid = os.fork()
735 if pid:
735 if pid:
736 return pid
736 return pid
737
737
738 closefds([m.server.fileno(), m.repowatcher.fileno()])
738 closefds([m.server.fileno(), m.repowatcher.fileno()])
739 os.setsid()
739 os.setsid()
740
740
741 fd = os.open('/dev/null', os.O_RDONLY)
741 fd = os.open('/dev/null', os.O_RDONLY)
742 os.dup2(fd, 0)
742 os.dup2(fd, 0)
743 if fd > 0:
743 if fd > 0:
744 os.close(fd)
744 os.close(fd)
745
745
746 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
746 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
747 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
747 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
748 os.dup2(fd, 1)
748 os.dup2(fd, 1)
749 os.dup2(fd, 2)
749 os.dup2(fd, 2)
750 if fd > 2:
750 if fd > 2:
751 os.close(fd)
751 os.close(fd)
752
752
753 try:
753 try:
754 m.run()
754 m.run()
755 finally:
755 finally:
756 m.shutdown()
756 m.shutdown()
757 os._exit(0)
757 os._exit(0)
General Comments 0
You need to be logged in to leave comments. Login now