51 self.out = out |
51 self.out = out |
52 self.channel = channel |
52 self.channel = channel |
53 |
53 |
54 @property |
54 @property |
55 def name(self): |
55 def name(self): |
56 return '<%c-channel>' % self.channel |
56 return b'<%c-channel>' % self.channel |
57 |
57 |
58 def write(self, data): |
58 def write(self, data): |
59 if not data: |
59 if not data: |
60 return |
60 return |
61 # single write() to guarantee the same atomicity as the underlying file |
61 # single write() to guarantee the same atomicity as the underlying file |
62 self.out.write(struct.pack('>cI', self.channel, len(data)) + data) |
62 self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data) |
63 self.out.flush() |
63 self.out.flush() |
64 |
64 |
65 def __getattr__(self, attr): |
65 def __getattr__(self, attr): |
66 if attr in (r'isatty', r'fileno', r'tell', r'seek'): |
66 if attr in (r'isatty', r'fileno', r'tell', r'seek'): |
67 raise AttributeError(attr) |
67 raise AttributeError(attr) |
136 else: |
136 else: |
137 return self._read(size, self.channel) |
137 return self._read(size, self.channel) |
138 |
138 |
139 def _read(self, size, channel): |
139 def _read(self, size, channel): |
140 if not size: |
140 if not size: |
141 return '' |
141 return b'' |
142 assert size > 0 |
142 assert size > 0 |
143 |
143 |
144 # tell the client we need at most size bytes |
144 # tell the client we need at most size bytes |
145 self.out.write(struct.pack('>cI', channel, size)) |
145 self.out.write(struct.pack(b'>cI', channel, size)) |
146 self.out.flush() |
146 self.out.flush() |
147 |
147 |
148 length = self.in_.read(4) |
148 length = self.in_.read(4) |
149 length = struct.unpack('>I', length)[0] |
149 length = struct.unpack(b'>I', length)[0] |
150 if not length: |
150 if not length: |
151 return '' |
151 return b'' |
152 else: |
152 else: |
153 return self.in_.read(length) |
153 return self.in_.read(length) |
154 |
154 |
155 def readline(self, size=-1): |
155 def readline(self, size=-1): |
156 if size < 0: |
156 if size < 0: |
157 size = self.maxchunksize |
157 size = self.maxchunksize |
158 s = self._read(size, 'L') |
158 s = self._read(size, b'L') |
159 buf = s |
159 buf = s |
160 # keep asking for more until there's either no more or |
160 # keep asking for more until there's either no more or |
161 # we got a full line |
161 # we got a full line |
162 while s and s[-1] != '\n': |
162 while s and s[-1] != b'\n': |
163 s = self._read(size, 'L') |
163 s = self._read(size, b'L') |
164 buf += s |
164 buf += s |
165 |
165 |
166 return buf |
166 return buf |
167 else: |
167 else: |
168 return self._read(size, 'L') |
168 return self._read(size, b'L') |
169 |
169 |
170 def __iter__(self): |
170 def __iter__(self): |
171 return self |
171 return self |
172 |
172 |
173 def next(self): |
173 def next(self): |
219 else: |
219 else: |
220 self.ui = ui |
220 self.ui = ui |
221 self.repo = self.repoui = None |
221 self.repo = self.repoui = None |
222 self._prereposetups = prereposetups |
222 self._prereposetups = prereposetups |
223 |
223 |
224 self.cdebug = channeledoutput(fout, 'd') |
224 self.cdebug = channeledoutput(fout, b'd') |
225 self.cerr = channeledoutput(fout, 'e') |
225 self.cerr = channeledoutput(fout, b'e') |
226 self.cout = channeledoutput(fout, 'o') |
226 self.cout = channeledoutput(fout, b'o') |
227 self.cin = channeledinput(fin, fout, 'I') |
227 self.cin = channeledinput(fin, fout, b'I') |
228 self.cresult = channeledoutput(fout, 'r') |
228 self.cresult = channeledoutput(fout, b'r') |
229 |
229 |
230 if self.ui.config(b'cmdserver', b'log') == b'-': |
230 if self.ui.config(b'cmdserver', b'log') == b'-': |
231 # switch log stream of server's ui to the 'd' (debug) channel |
231 # switch log stream of server's ui to the 'd' (debug) channel |
232 # (don't touch repo.ui as its lifetime is longer than the server) |
232 # (don't touch repo.ui as its lifetime is longer than the server) |
233 self.ui = self.ui.copy() |
233 self.ui = self.ui.copy() |
262 """read a string from the channel |
262 """read a string from the channel |
263 |
263 |
264 format: |
264 format: |
265 data length (uint32), data |
265 data length (uint32), data |
266 """ |
266 """ |
267 length = struct.unpack('>I', self._read(4))[0] |
267 length = struct.unpack(b'>I', self._read(4))[0] |
268 if not length: |
268 if not length: |
269 return '' |
269 return b'' |
270 return self._read(length) |
270 return self._read(length) |
271 |
271 |
272 def _readlist(self): |
272 def _readlist(self): |
273 """read a list of NULL separated strings from the channel""" |
273 """read a list of NULL separated strings from the channel""" |
274 s = self._readstr() |
274 s = self._readstr() |
275 if s: |
275 if s: |
276 return s.split('\0') |
276 return s.split(b'\0') |
277 else: |
277 else: |
278 return [] |
278 return [] |
279 |
279 |
280 def runcommand(self): |
280 def runcommand(self): |
281 """ reads a list of \0 terminated arguments, executes |
281 """ reads a list of \0 terminated arguments, executes |
300 for ui in uis: |
300 for ui in uis: |
301 ui.resetstate() |
301 ui.resetstate() |
302 # any kind of interaction must use server channels, but chg may |
302 # any kind of interaction must use server channels, but chg may |
303 # replace channels by fully functional tty files. so nontty is |
303 # replace channels by fully functional tty files. so nontty is |
304 # enforced only if cin is a channel. |
304 # enforced only if cin is a channel. |
305 if not util.safehasattr(self.cin, 'fileno'): |
305 if not util.safehasattr(self.cin, b'fileno'): |
306 ui.setconfig('ui', 'nontty', 'true', 'commandserver') |
306 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver') |
307 |
307 |
308 req = dispatch.request( |
308 req = dispatch.request( |
309 args[:], |
309 args[:], |
310 copiedui, |
310 copiedui, |
311 self.repo, |
311 self.repo, |
335 if handler: |
335 if handler: |
336 handler(self) |
336 handler(self) |
337 else: |
337 else: |
338 # clients are expected to check what commands are supported by |
338 # clients are expected to check what commands are supported by |
339 # looking at the servers capabilities |
339 # looking at the servers capabilities |
340 raise error.Abort(_('unknown command %s') % cmd) |
340 raise error.Abort(_(b'unknown command %s') % cmd) |
341 |
341 |
342 return cmd != '' |
342 return cmd != b'' |
343 |
343 |
344 capabilities = {'runcommand': runcommand, 'getencoding': getencoding} |
344 capabilities = {b'runcommand': runcommand, b'getencoding': getencoding} |
345 |
345 |
346 def serve(self): |
346 def serve(self): |
347 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities)) |
347 hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities)) |
348 hellomsg += '\n' |
348 hellomsg += b'\n' |
349 hellomsg += 'encoding: ' + encoding.encoding |
349 hellomsg += b'encoding: ' + encoding.encoding |
350 hellomsg += '\n' |
350 hellomsg += b'\n' |
351 if self.cmsg: |
351 if self.cmsg: |
352 hellomsg += 'message-encoding: %s\n' % self.cmsg.encoding |
352 hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding |
353 hellomsg += 'pid: %d' % procutil.getpid() |
353 hellomsg += b'pid: %d' % procutil.getpid() |
354 if util.safehasattr(os, 'getpgid'): |
354 if util.safehasattr(os, b'getpgid'): |
355 hellomsg += '\n' |
355 hellomsg += b'\n' |
356 hellomsg += 'pgid: %d' % os.getpgid(0) |
356 hellomsg += b'pgid: %d' % os.getpgid(0) |
357 |
357 |
358 # write the hello msg in -one- chunk |
358 # write the hello msg in -one- chunk |
359 self.cout.write(hellomsg) |
359 self.cout.write(hellomsg) |
360 |
360 |
361 try: |
361 try: |
457 try: |
457 try: |
458 sv.serve() |
458 sv.serve() |
459 # handle exceptions that may be raised by command server. most of |
459 # handle exceptions that may be raised by command server. most of |
460 # known exceptions are caught by dispatch. |
460 # known exceptions are caught by dispatch. |
461 except error.Abort as inst: |
461 except error.Abort as inst: |
462 ui.error(_('abort: %s\n') % inst) |
462 ui.error(_(b'abort: %s\n') % inst) |
463 except IOError as inst: |
463 except IOError as inst: |
464 if inst.errno != errno.EPIPE: |
464 if inst.errno != errno.EPIPE: |
465 raise |
465 raise |
466 except KeyboardInterrupt: |
466 except KeyboardInterrupt: |
467 pass |
467 pass |
498 self.ui = ui |
498 self.ui = ui |
499 |
499 |
500 def bindsocket(self, sock, address): |
500 def bindsocket(self, sock, address): |
501 util.bindunixsocket(sock, address) |
501 util.bindunixsocket(sock, address) |
502 sock.listen(socket.SOMAXCONN) |
502 sock.listen(socket.SOMAXCONN) |
503 self.ui.status(_('listening at %s\n') % address) |
503 self.ui.status(_(b'listening at %s\n') % address) |
504 self.ui.flush() # avoid buffering of status message |
504 self.ui.flush() # avoid buffering of status message |
505 |
505 |
506 def unlinksocket(self, address): |
506 def unlinksocket(self, address): |
507 os.unlink(address) |
507 os.unlink(address) |
508 |
508 |
525 """ |
525 """ |
526 |
526 |
527 def __init__(self, ui, repo, opts, handler=None): |
527 def __init__(self, ui, repo, opts, handler=None): |
528 self.ui = ui |
528 self.ui = ui |
529 self.repo = repo |
529 self.repo = repo |
530 self.address = opts['address'] |
530 self.address = opts[b'address'] |
531 if not util.safehasattr(socket, 'AF_UNIX'): |
531 if not util.safehasattr(socket, b'AF_UNIX'): |
532 raise error.Abort(_('unsupported platform')) |
532 raise error.Abort(_(b'unsupported platform')) |
533 if not self.address: |
533 if not self.address: |
534 raise error.Abort(_('no socket path specified with --address')) |
534 raise error.Abort(_(b'no socket path specified with --address')) |
535 self._servicehandler = handler or unixservicehandler(ui) |
535 self._servicehandler = handler or unixservicehandler(ui) |
536 self._sock = None |
536 self._sock = None |
537 self._mainipc = None |
537 self._mainipc = None |
538 self._workeripc = None |
538 self._workeripc = None |
539 self._oldsigchldhandler = None |
539 self._oldsigchldhandler = None |
540 self._workerpids = set() # updated by signal handler; do not iterate |
540 self._workerpids = set() # updated by signal handler; do not iterate |
541 self._socketunlinked = None |
541 self._socketunlinked = None |
542 # experimental config: cmdserver.max-repo-cache |
542 # experimental config: cmdserver.max-repo-cache |
543 maxlen = ui.configint(b'cmdserver', b'max-repo-cache') |
543 maxlen = ui.configint(b'cmdserver', b'max-repo-cache') |
544 if maxlen < 0: |
544 if maxlen < 0: |
545 raise error.Abort(_('negative max-repo-cache size not allowed')) |
545 raise error.Abort(_(b'negative max-repo-cache size not allowed')) |
546 self._repoloader = repocache.repoloader(ui, maxlen) |
546 self._repoloader = repocache.repoloader(ui, maxlen) |
547 |
547 |
548 def init(self): |
548 def init(self): |
549 self._sock = socket.socket(socket.AF_UNIX) |
549 self._sock = socket.socket(socket.AF_UNIX) |
550 # IPC channel from many workers to one main process; this is actually |
550 # IPC channel from many workers to one main process; this is actually |
551 # a uni-directional pipe, but is backed by a DGRAM socket so each |
551 # a uni-directional pipe, but is backed by a DGRAM socket so each |
552 # message can be easily separated. |
552 # message can be easily separated. |
553 o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) |
553 o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) |
554 self._mainipc, self._workeripc = o |
554 self._mainipc, self._workeripc = o |
555 self._servicehandler.bindsocket(self._sock, self.address) |
555 self._servicehandler.bindsocket(self._sock, self.address) |
556 if util.safehasattr(procutil, 'unblocksignal'): |
556 if util.safehasattr(procutil, b'unblocksignal'): |
557 procutil.unblocksignal(signal.SIGCHLD) |
557 procutil.unblocksignal(signal.SIGCHLD) |
558 o = signal.signal(signal.SIGCHLD, self._sigchldhandler) |
558 o = signal.signal(signal.SIGCHLD, self._sigchldhandler) |
559 self._oldsigchldhandler = o |
559 self._oldsigchldhandler = o |
560 self._socketunlinked = False |
560 self._socketunlinked = False |
561 self._repoloader.start() |
561 self._repoloader.start() |