Show More
This diff has been collapsed as it changes many lines, (779 lines changed) Show them Hide them | |||||
@@ -0,0 +1,779 b'' | |||||
|
1 | # Copyright 2014-present Facebook, Inc. | |||
|
2 | # All rights reserved. | |||
|
3 | # | |||
|
4 | # Redistribution and use in source and binary forms, with or without | |||
|
5 | # modification, are permitted provided that the following conditions are met: | |||
|
6 | # | |||
|
7 | # * Redistributions of source code must retain the above copyright notice, | |||
|
8 | # this list of conditions and the following disclaimer. | |||
|
9 | # | |||
|
10 | # * Redistributions in binary form must reproduce the above copyright notice, | |||
|
11 | # this list of conditions and the following disclaimer in the documentation | |||
|
12 | # and/or other materials provided with the distribution. | |||
|
13 | # | |||
|
14 | # * Neither the name Facebook nor the names of its contributors may be used to | |||
|
15 | # endorse or promote products derived from this software without specific | |||
|
16 | # prior written permission. | |||
|
17 | # | |||
|
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
|
19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
|
20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
|
21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
|
22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
|
23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
|
24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
|
25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
|
26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
|
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
|
28 | ||||
|
29 | import os | |||
|
30 | import errno | |||
|
31 | import math | |||
|
32 | import socket | |||
|
33 | import subprocess | |||
|
34 | import time | |||
|
35 | ||||
|
36 | # Sometimes it's really hard to get Python extensions to compile, | |||
|
37 | # so fall back to a pure Python implementation. | |||
|
38 | try: | |||
|
39 | import bser | |||
|
40 | except ImportError: | |||
|
41 | import pybser as bser | |||
|
42 | ||||
|
43 | import capabilities | |||
|
44 | ||||
|
45 | if os.name == 'nt': | |||
|
46 | import ctypes | |||
|
47 | import ctypes.wintypes | |||
|
48 | ||||
|
49 | wintypes = ctypes.wintypes | |||
|
50 | GENERIC_READ = 0x80000000 | |||
|
51 | GENERIC_WRITE = 0x40000000 | |||
|
52 | FILE_FLAG_OVERLAPPED = 0x40000000 | |||
|
53 | OPEN_EXISTING = 3 | |||
|
54 | INVALID_HANDLE_VALUE = -1 | |||
|
55 | FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 | |||
|
56 | FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 | |||
|
57 | FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 | |||
|
58 | WAIT_TIMEOUT = 0x00000102 | |||
|
59 | WAIT_OBJECT_0 = 0x00000000 | |||
|
60 | ERROR_IO_PENDING = 997 | |||
|
61 | ||||
|
62 | class OVERLAPPED(ctypes.Structure): | |||
|
63 | _fields_ = [ | |||
|
64 | ("Internal", wintypes.ULONG), ("InternalHigh", wintypes.ULONG), | |||
|
65 | ("Offset", wintypes.DWORD), ("OffsetHigh", wintypes.DWORD), | |||
|
66 | ("hEvent", wintypes.HANDLE) | |||
|
67 | ] | |||
|
68 | ||||
|
69 | def __init__(self): | |||
|
70 | self.Offset = 0 | |||
|
71 | self.OffsetHigh = 0 | |||
|
72 | self.hEvent = 0 | |||
|
73 | ||||
|
74 | LPDWORD = ctypes.POINTER(wintypes.DWORD) | |||
|
75 | ||||
|
76 | CreateFile = ctypes.windll.kernel32.CreateFileA | |||
|
77 | CreateFile.argtypes = [wintypes.LPSTR, wintypes.DWORD, wintypes.DWORD, | |||
|
78 | wintypes.LPVOID, wintypes.DWORD, wintypes.DWORD, | |||
|
79 | wintypes.HANDLE] | |||
|
80 | CreateFile.restype = wintypes.HANDLE | |||
|
81 | ||||
|
82 | CloseHandle = ctypes.windll.kernel32.CloseHandle | |||
|
83 | CloseHandle.argtypes = [wintypes.HANDLE] | |||
|
84 | CloseHandle.restype = wintypes.BOOL | |||
|
85 | ||||
|
86 | ReadFile = ctypes.windll.kernel32.ReadFile | |||
|
87 | ReadFile.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, | |||
|
88 | LPDWORD, ctypes.POINTER(OVERLAPPED)] | |||
|
89 | ReadFile.restype = wintypes.BOOL | |||
|
90 | ||||
|
91 | WriteFile = ctypes.windll.kernel32.WriteFile | |||
|
92 | WriteFile.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, | |||
|
93 | LPDWORD, ctypes.POINTER(OVERLAPPED)] | |||
|
94 | WriteFile.restype = wintypes.BOOL | |||
|
95 | ||||
|
96 | GetLastError = ctypes.windll.kernel32.GetLastError | |||
|
97 | GetLastError.argtypes = [] | |||
|
98 | GetLastError.restype = wintypes.DWORD | |||
|
99 | ||||
|
100 | FormatMessage = ctypes.windll.kernel32.FormatMessageA | |||
|
101 | FormatMessage.argtypes = [wintypes.DWORD, wintypes.LPVOID, wintypes.DWORD, | |||
|
102 | wintypes.DWORD, ctypes.POINTER(wintypes.LPSTR), | |||
|
103 | wintypes.DWORD, wintypes.LPVOID] | |||
|
104 | FormatMessage.restype = wintypes.DWORD | |||
|
105 | ||||
|
106 | LocalFree = ctypes.windll.kernel32.LocalFree | |||
|
107 | ||||
|
108 | GetOverlappedResultEx = ctypes.windll.kernel32.GetOverlappedResultEx | |||
|
109 | GetOverlappedResultEx.argtypes = [wintypes.HANDLE, | |||
|
110 | ctypes.POINTER(OVERLAPPED), LPDWORD, | |||
|
111 | wintypes.DWORD, wintypes.BOOL] | |||
|
112 | GetOverlappedResultEx.restype = wintypes.BOOL | |||
|
113 | ||||
|
114 | CancelIoEx = ctypes.windll.kernel32.CancelIoEx | |||
|
115 | CancelIoEx.argtypes = [wintypes.HANDLE, ctypes.POINTER(OVERLAPPED)] | |||
|
116 | CancelIoEx.restype = wintypes.BOOL | |||
|
117 | ||||
|
118 | # 2 bytes marker, 1 byte int size, 8 bytes int64 value | |||
|
119 | sniff_len = 13 | |||
|
120 | ||||
|
121 | # This is a helper for debugging the client. | |||
|
122 | _debugging = False | |||
|
123 | if _debugging: | |||
|
124 | ||||
|
125 | def log(fmt, *args): | |||
|
126 | print('[%s] %s' % | |||
|
127 | (time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime()), | |||
|
128 | fmt % args[:])) | |||
|
129 | else: | |||
|
130 | ||||
|
131 | def log(fmt, *args): | |||
|
132 | pass | |||
|
133 | ||||
|
134 | ||||
|
135 | class WatchmanError(Exception): | |||
|
136 | pass | |||
|
137 | ||||
|
138 | ||||
|
139 | class SocketTimeout(WatchmanError): | |||
|
140 | """A specialized exception raised for socket timeouts during communication to/from watchman. | |||
|
141 | This makes it easier to implement non-blocking loops as callers can easily distinguish | |||
|
142 | between a routine timeout and an actual error condition. | |||
|
143 | ||||
|
144 | Note that catching WatchmanError will also catch this as it is a super-class, so backwards | |||
|
145 | compatibility in exception handling is preserved. | |||
|
146 | """ | |||
|
147 | ||||
|
148 | ||||
|
149 | class CommandError(WatchmanError): | |||
|
150 | """error returned by watchman | |||
|
151 | ||||
|
152 | self.msg is the message returned by watchman. | |||
|
153 | """ | |||
|
154 | ||||
|
155 | def __init__(self, msg, cmd=None): | |||
|
156 | self.msg = msg | |||
|
157 | self.cmd = cmd | |||
|
158 | super(CommandError, self).__init__('watchman command error: %s' % msg) | |||
|
159 | ||||
|
160 | def setCommand(self, cmd): | |||
|
161 | self.cmd = cmd | |||
|
162 | ||||
|
163 | def __str__(self): | |||
|
164 | if self.cmd: | |||
|
165 | return '%s, while executing %s' % (self.msg, self.cmd) | |||
|
166 | return self.msg | |||
|
167 | ||||
|
168 | ||||
|
169 | class Transport(object): | |||
|
170 | """ communication transport to the watchman server """ | |||
|
171 | buf = None | |||
|
172 | ||||
|
173 | def close(self): | |||
|
174 | """ tear it down """ | |||
|
175 | raise NotImplementedError() | |||
|
176 | ||||
|
177 | def readBytes(self, size): | |||
|
178 | """ read size bytes """ | |||
|
179 | raise NotImplementedError() | |||
|
180 | ||||
|
181 | def write(self, buf): | |||
|
182 | """ write some data """ | |||
|
183 | raise NotImplementedError() | |||
|
184 | ||||
|
185 | def setTimeout(self, value): | |||
|
186 | pass | |||
|
187 | ||||
|
188 | def readLine(self): | |||
|
189 | """ read a line | |||
|
190 | Maintains its own buffer, callers of the transport should not mix | |||
|
191 | calls to readBytes and readLine. | |||
|
192 | """ | |||
|
193 | if self.buf is None: | |||
|
194 | self.buf = [] | |||
|
195 | ||||
|
196 | # Buffer may already have a line if we've received unilateral | |||
|
197 | # response(s) from the server | |||
|
198 | if len(self.buf) == 1 and "\n" in self.buf[0]: | |||
|
199 | (line, b) = self.buf[0].split("\n", 1) | |||
|
200 | self.buf = [b] | |||
|
201 | return line | |||
|
202 | ||||
|
203 | while True: | |||
|
204 | b = self.readBytes(4096) | |||
|
205 | if "\n" in b: | |||
|
206 | result = ''.join(self.buf) | |||
|
207 | (line, b) = b.split("\n", 1) | |||
|
208 | self.buf = [b] | |||
|
209 | return result + line | |||
|
210 | self.buf.append(b) | |||
|
211 | ||||
|
212 | ||||
|
213 | class Codec(object): | |||
|
214 | """ communication encoding for the watchman server """ | |||
|
215 | transport = None | |||
|
216 | ||||
|
217 | def __init__(self, transport): | |||
|
218 | self.transport = transport | |||
|
219 | ||||
|
220 | def receive(self): | |||
|
221 | raise NotImplementedError() | |||
|
222 | ||||
|
223 | def send(self, *args): | |||
|
224 | raise NotImplementedError() | |||
|
225 | ||||
|
226 | def setTimeout(self, value): | |||
|
227 | self.transport.setTimeout(value) | |||
|
228 | ||||
|
229 | ||||
|
230 | class UnixSocketTransport(Transport): | |||
|
231 | """ local unix domain socket transport """ | |||
|
232 | sock = None | |||
|
233 | ||||
|
234 | def __init__(self, sockpath, timeout): | |||
|
235 | self.sockpath = sockpath | |||
|
236 | self.timeout = timeout | |||
|
237 | ||||
|
238 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | |||
|
239 | try: | |||
|
240 | sock.settimeout(self.timeout) | |||
|
241 | sock.connect(self.sockpath) | |||
|
242 | self.sock = sock | |||
|
243 | except socket.error as e: | |||
|
244 | raise WatchmanError('unable to connect to %s: %s' % | |||
|
245 | (self.sockpath, e)) | |||
|
246 | ||||
|
247 | def close(self): | |||
|
248 | self.sock.close() | |||
|
249 | self.sock = None | |||
|
250 | ||||
|
251 | def setTimeout(self, value): | |||
|
252 | self.timeout = value | |||
|
253 | self.sock.settimeout(self.timeout) | |||
|
254 | ||||
|
255 | def readBytes(self, size): | |||
|
256 | try: | |||
|
257 | buf = [self.sock.recv(size)] | |||
|
258 | if not buf[0]: | |||
|
259 | raise WatchmanError('empty watchman response') | |||
|
260 | return buf[0] | |||
|
261 | except socket.timeout: | |||
|
262 | raise SocketTimeout('timed out waiting for response') | |||
|
263 | ||||
|
264 | def write(self, data): | |||
|
265 | try: | |||
|
266 | self.sock.sendall(data) | |||
|
267 | except socket.timeout: | |||
|
268 | raise SocketTimeout('timed out sending query command') | |||
|
269 | ||||
|
270 | ||||
|
271 | class WindowsNamedPipeTransport(Transport): | |||
|
272 | """ connect to a named pipe """ | |||
|
273 | ||||
|
274 | def __init__(self, sockpath, timeout): | |||
|
275 | self.sockpath = sockpath | |||
|
276 | self.timeout = int(math.ceil(timeout * 1000)) | |||
|
277 | self._iobuf = None | |||
|
278 | ||||
|
279 | self.pipe = CreateFile(sockpath, GENERIC_READ | GENERIC_WRITE, 0, None, | |||
|
280 | OPEN_EXISTING, FILE_FLAG_OVERLAPPED, None) | |||
|
281 | ||||
|
282 | if self.pipe == INVALID_HANDLE_VALUE: | |||
|
283 | self.pipe = None | |||
|
284 | self._raise_win_err('failed to open pipe %s' % sockpath, | |||
|
285 | GetLastError()) | |||
|
286 | ||||
|
287 | def _win32_strerror(self, err): | |||
|
288 | """ expand a win32 error code into a human readable message """ | |||
|
289 | ||||
|
290 | # FormatMessage will allocate memory and assign it here | |||
|
291 | buf = ctypes.c_char_p() | |||
|
292 | FormatMessage( | |||
|
293 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | |||
|
294 | | FORMAT_MESSAGE_IGNORE_INSERTS, None, err, 0, buf, 0, None) | |||
|
295 | try: | |||
|
296 | return buf.value | |||
|
297 | finally: | |||
|
298 | LocalFree(buf) | |||
|
299 | ||||
|
300 | def _raise_win_err(self, msg, err): | |||
|
301 | raise IOError('%s win32 error code: %d %s' % | |||
|
302 | (msg, err, self._win32_strerror(err))) | |||
|
303 | ||||
|
304 | def close(self): | |||
|
305 | if self.pipe: | |||
|
306 | CloseHandle(self.pipe) | |||
|
307 | self.pipe = None | |||
|
308 | ||||
|
309 | def readBytes(self, size): | |||
|
310 | """ A read can block for an unbounded amount of time, even if the | |||
|
311 | kernel reports that the pipe handle is signalled, so we need to | |||
|
312 | always perform our reads asynchronously | |||
|
313 | """ | |||
|
314 | ||||
|
315 | # try to satisfy the read from any buffered data | |||
|
316 | if self._iobuf: | |||
|
317 | if size >= len(self._iobuf): | |||
|
318 | res = self._iobuf | |||
|
319 | self.buf = None | |||
|
320 | return res | |||
|
321 | res = self._iobuf[:size] | |||
|
322 | self._iobuf = self._iobuf[size:] | |||
|
323 | return res | |||
|
324 | ||||
|
325 | # We need to initiate a read | |||
|
326 | buf = ctypes.create_string_buffer(size) | |||
|
327 | olap = OVERLAPPED() | |||
|
328 | ||||
|
329 | log('made read buff of size %d', size) | |||
|
330 | ||||
|
331 | # ReadFile docs warn against sending in the nread parameter for async | |||
|
332 | # operations, so we always collect it via GetOverlappedResultEx | |||
|
333 | immediate = ReadFile(self.pipe, buf, size, None, olap) | |||
|
334 | ||||
|
335 | if not immediate: | |||
|
336 | err = GetLastError() | |||
|
337 | if err != ERROR_IO_PENDING: | |||
|
338 | self._raise_win_err('failed to read %d bytes' % size, | |||
|
339 | GetLastError()) | |||
|
340 | ||||
|
341 | nread = wintypes.DWORD() | |||
|
342 | if not GetOverlappedResultEx(self.pipe, olap, nread, | |||
|
343 | 0 if immediate else self.timeout, True): | |||
|
344 | err = GetLastError() | |||
|
345 | CancelIoEx(self.pipe, olap) | |||
|
346 | ||||
|
347 | if err == WAIT_TIMEOUT: | |||
|
348 | log('GetOverlappedResultEx timedout') | |||
|
349 | raise SocketTimeout('timed out after waiting %dms for read' % | |||
|
350 | self.timeout) | |||
|
351 | ||||
|
352 | log('GetOverlappedResultEx reports error %d', err) | |||
|
353 | self._raise_win_err('error while waiting for read', err) | |||
|
354 | ||||
|
355 | nread = nread.value | |||
|
356 | if nread == 0: | |||
|
357 | # Docs say that named pipes return 0 byte when the other end did | |||
|
358 | # a zero byte write. Since we don't ever do that, the only | |||
|
359 | # other way this shows up is if the client has gotten in a weird | |||
|
360 | # state, so let's bail out | |||
|
361 | CancelIoEx(self.pipe, olap) | |||
|
362 | raise IOError('Async read yielded 0 bytes; unpossible!') | |||
|
363 | ||||
|
364 | # Holds precisely the bytes that we read from the prior request | |||
|
365 | buf = buf[:nread] | |||
|
366 | ||||
|
367 | returned_size = min(nread, size) | |||
|
368 | if returned_size == nread: | |||
|
369 | return buf | |||
|
370 | ||||
|
371 | # keep any left-overs around for a later read to consume | |||
|
372 | self._iobuf = buf[returned_size:] | |||
|
373 | return buf[:returned_size] | |||
|
374 | ||||
|
375 | def write(self, data): | |||
|
376 | olap = OVERLAPPED() | |||
|
377 | immediate = WriteFile(self.pipe, ctypes.c_char_p(data), len(data), | |||
|
378 | None, olap) | |||
|
379 | ||||
|
380 | if not immediate: | |||
|
381 | err = GetLastError() | |||
|
382 | if err != ERROR_IO_PENDING: | |||
|
383 | self._raise_win_err('failed to write %d bytes' % len(data), | |||
|
384 | GetLastError()) | |||
|
385 | ||||
|
386 | # Obtain results, waiting if needed | |||
|
387 | nwrote = wintypes.DWORD() | |||
|
388 | if GetOverlappedResultEx(self.pipe, olap, nwrote, 0 if immediate else | |||
|
389 | self.timeout, True): | |||
|
390 | return nwrote.value | |||
|
391 | ||||
|
392 | err = GetLastError() | |||
|
393 | ||||
|
394 | # It's potentially unsafe to allow the write to continue after | |||
|
395 | # we unwind, so let's make a best effort to avoid that happening | |||
|
396 | CancelIoEx(self.pipe, olap) | |||
|
397 | ||||
|
398 | if err == WAIT_TIMEOUT: | |||
|
399 | raise SocketTimeout('timed out after waiting %dms for write' % | |||
|
400 | self.timeout) | |||
|
401 | self._raise_win_err('error while waiting for write of %d bytes' % | |||
|
402 | len(data), err) | |||
|
403 | ||||
|
404 | ||||
|
405 | class CLIProcessTransport(Transport): | |||
|
406 | """ open a pipe to the cli to talk to the service | |||
|
407 | This intended to be used only in the test harness! | |||
|
408 | ||||
|
409 | The CLI is an oddball because we only support JSON input | |||
|
410 | and cannot send multiple commands through the same instance, | |||
|
411 | so we spawn a new process for each command. | |||
|
412 | ||||
|
413 | We disable server spawning for this implementation, again, because | |||
|
414 | it is intended to be used only in our test harness. You really | |||
|
415 | should not need to use the CLI transport for anything real. | |||
|
416 | ||||
|
417 | While the CLI can output in BSER, our Transport interface doesn't | |||
|
418 | support telling this instance that it should do so. That effectively | |||
|
419 | limits this implementation to JSON input and output only at this time. | |||
|
420 | ||||
|
421 | It is the responsibility of the caller to set the send and | |||
|
422 | receive codecs appropriately. | |||
|
423 | """ | |||
|
424 | proc = None | |||
|
425 | closed = True | |||
|
426 | ||||
|
427 | def __init__(self, sockpath, timeout): | |||
|
428 | self.sockpath = sockpath | |||
|
429 | self.timeout = timeout | |||
|
430 | ||||
|
431 | def close(self): | |||
|
432 | if self.proc: | |||
|
433 | self.proc.kill() | |||
|
434 | self.proc = None | |||
|
435 | ||||
|
436 | def _connect(self): | |||
|
437 | if self.proc: | |||
|
438 | return self.proc | |||
|
439 | args = [ | |||
|
440 | 'watchman', | |||
|
441 | '--sockname={}'.format(self.sockpath), | |||
|
442 | '--logfile=/BOGUS', | |||
|
443 | '--statefile=/BOGUS', | |||
|
444 | '--no-spawn', | |||
|
445 | '--no-local', | |||
|
446 | '--no-pretty', | |||
|
447 | '-j', | |||
|
448 | ] | |||
|
449 | self.proc = subprocess.Popen(args, | |||
|
450 | stdin=subprocess.PIPE, | |||
|
451 | stdout=subprocess.PIPE) | |||
|
452 | return self.proc | |||
|
453 | ||||
|
454 | def readBytes(self, size): | |||
|
455 | self._connect() | |||
|
456 | res = self.proc.stdout.read(size) | |||
|
457 | if res == '': | |||
|
458 | raise WatchmanError('EOF on CLI process transport') | |||
|
459 | return res | |||
|
460 | ||||
|
461 | def write(self, data): | |||
|
462 | if self.closed: | |||
|
463 | self.closed = False | |||
|
464 | self.proc = None | |||
|
465 | self._connect() | |||
|
466 | res = self.proc.stdin.write(data) | |||
|
467 | self.proc.stdin.close() | |||
|
468 | self.closed = True | |||
|
469 | return res | |||
|
470 | ||||
|
471 | ||||
|
472 | class BserCodec(Codec): | |||
|
473 | """ use the BSER encoding. This is the default, preferred codec """ | |||
|
474 | ||||
|
475 | def _loads(self, response): | |||
|
476 | return bser.loads(response) | |||
|
477 | ||||
|
478 | def receive(self): | |||
|
479 | buf = [self.transport.readBytes(sniff_len)] | |||
|
480 | if not buf[0]: | |||
|
481 | raise WatchmanError('empty watchman response') | |||
|
482 | ||||
|
483 | elen = bser.pdu_len(buf[0]) | |||
|
484 | ||||
|
485 | rlen = len(buf[0]) | |||
|
486 | while elen > rlen: | |||
|
487 | buf.append(self.transport.readBytes(elen - rlen)) | |||
|
488 | rlen += len(buf[-1]) | |||
|
489 | ||||
|
490 | response = ''.join(buf) | |||
|
491 | try: | |||
|
492 | res = self._loads(response) | |||
|
493 | return res | |||
|
494 | except ValueError as e: | |||
|
495 | raise WatchmanError('watchman response decode error: %s' % e) | |||
|
496 | ||||
|
497 | def send(self, *args): | |||
|
498 | cmd = bser.dumps(*args) | |||
|
499 | self.transport.write(cmd) | |||
|
500 | ||||
|
501 | ||||
|
502 | class ImmutableBserCodec(BserCodec): | |||
|
503 | """ use the BSER encoding, decoding values using the newer | |||
|
504 | immutable object support """ | |||
|
505 | ||||
|
506 | def _loads(self, response): | |||
|
507 | return bser.loads(response, False) | |||
|
508 | ||||
|
509 | ||||
|
510 | class JsonCodec(Codec): | |||
|
511 | """ Use json codec. This is here primarily for testing purposes """ | |||
|
512 | json = None | |||
|
513 | ||||
|
514 | def __init__(self, transport): | |||
|
515 | super(JsonCodec, self).__init__(transport) | |||
|
516 | # optional dep on json, only if JsonCodec is used | |||
|
517 | import json | |||
|
518 | self.json = json | |||
|
519 | ||||
|
520 | def receive(self): | |||
|
521 | line = self.transport.readLine() | |||
|
522 | try: | |||
|
523 | return self.json.loads(line) | |||
|
524 | except Exception as e: | |||
|
525 | print(e, line) | |||
|
526 | raise | |||
|
527 | ||||
|
528 | def send(self, *args): | |||
|
529 | cmd = self.json.dumps(*args) | |||
|
530 | self.transport.write(cmd + "\n") | |||
|
531 | ||||
|
532 | ||||
|
533 | class client(object): | |||
|
534 | """ Handles the communication with the watchman service """ | |||
|
535 | sockpath = None | |||
|
536 | transport = None | |||
|
537 | sendCodec = None | |||
|
538 | recvCodec = None | |||
|
539 | sendConn = None | |||
|
540 | recvConn = None | |||
|
541 | subs = {} # Keyed by subscription name | |||
|
542 | sub_by_root = {} # Keyed by root, then by subscription name | |||
|
543 | logs = [] # When log level is raised | |||
|
544 | unilateral = ['log', 'subscription'] | |||
|
545 | tport = None | |||
|
546 | useImmutableBser = None | |||
|
547 | ||||
|
548 | def __init__(self, | |||
|
549 | sockpath=None, | |||
|
550 | timeout=1.0, | |||
|
551 | transport=None, | |||
|
552 | sendEncoding=None, | |||
|
553 | recvEncoding=None, | |||
|
554 | useImmutableBser=False): | |||
|
555 | self.sockpath = sockpath | |||
|
556 | self.timeout = timeout | |||
|
557 | self.useImmutableBser = useImmutableBser | |||
|
558 | ||||
|
559 | transport = transport or os.getenv('WATCHMAN_TRANSPORT') or 'local' | |||
|
560 | if transport == 'local' and os.name == 'nt': | |||
|
561 | self.transport = WindowsNamedPipeTransport | |||
|
562 | elif transport == 'local': | |||
|
563 | self.transport = UnixSocketTransport | |||
|
564 | elif transport == 'cli': | |||
|
565 | self.transport = CLIProcessTransport | |||
|
566 | if sendEncoding is None: | |||
|
567 | sendEncoding = 'json' | |||
|
568 | if recvEncoding is None: | |||
|
569 | recvEncoding = sendEncoding | |||
|
570 | else: | |||
|
571 | raise WatchmanError('invalid transport %s' % transport) | |||
|
572 | ||||
|
573 | sendEncoding = sendEncoding or os.getenv('WATCHMAN_ENCODING') or 'bser' | |||
|
574 | recvEncoding = recvEncoding or os.getenv('WATCHMAN_ENCODING') or 'bser' | |||
|
575 | ||||
|
576 | self.recvCodec = self._parseEncoding(recvEncoding) | |||
|
577 | self.sendCodec = self._parseEncoding(sendEncoding) | |||
|
578 | ||||
|
579 | def _parseEncoding(self, enc): | |||
|
580 | if enc == 'bser': | |||
|
581 | if self.useImmutableBser: | |||
|
582 | return ImmutableBserCodec | |||
|
583 | return BserCodec | |||
|
584 | elif enc == 'json': | |||
|
585 | return JsonCodec | |||
|
586 | else: | |||
|
587 | raise WatchmanError('invalid encoding %s' % enc) | |||
|
588 | ||||
|
589 | def _hasprop(self, result, name): | |||
|
590 | if self.useImmutableBser: | |||
|
591 | return hasattr(result, name) | |||
|
592 | return name in result | |||
|
593 | ||||
|
594 | def _resolvesockname(self): | |||
|
595 | # if invoked via a trigger, watchman will set this env var; we | |||
|
596 | # should use it unless explicitly set otherwise | |||
|
597 | path = os.getenv('WATCHMAN_SOCK') | |||
|
598 | if path: | |||
|
599 | return path | |||
|
600 | ||||
|
601 | cmd = ['watchman', '--output-encoding=bser', 'get-sockname'] | |||
|
602 | try: | |||
|
603 | p = subprocess.Popen(cmd, | |||
|
604 | stdout=subprocess.PIPE, | |||
|
605 | stderr=subprocess.PIPE, | |||
|
606 | close_fds=os.name != 'nt') | |||
|
607 | except OSError as e: | |||
|
608 | raise WatchmanError('"watchman" executable not in PATH (%s)', e) | |||
|
609 | ||||
|
610 | stdout, stderr = p.communicate() | |||
|
611 | exitcode = p.poll() | |||
|
612 | ||||
|
613 | if exitcode: | |||
|
614 | raise WatchmanError("watchman exited with code %d" % exitcode) | |||
|
615 | ||||
|
616 | result = bser.loads(stdout) | |||
|
617 | if 'error' in result: | |||
|
618 | raise WatchmanError('get-sockname error: %s' % result['error']) | |||
|
619 | ||||
|
620 | return result['sockname'] | |||
|
621 | ||||
|
622 | def _connect(self): | |||
|
623 | """ establish transport connection """ | |||
|
624 | ||||
|
625 | if self.recvConn: | |||
|
626 | return | |||
|
627 | ||||
|
628 | if self.sockpath is None: | |||
|
629 | self.sockpath = self._resolvesockname() | |||
|
630 | ||||
|
631 | self.tport = self.transport(self.sockpath, self.timeout) | |||
|
632 | self.sendConn = self.sendCodec(self.tport) | |||
|
633 | self.recvConn = self.recvCodec(self.tport) | |||
|
634 | ||||
|
635 | def __del__(self): | |||
|
636 | self.close() | |||
|
637 | ||||
|
638 | def close(self): | |||
|
639 | if self.tport: | |||
|
640 | self.tport.close() | |||
|
641 | self.tport = None | |||
|
642 | self.recvConn = None | |||
|
643 | self.sendConn = None | |||
|
644 | ||||
|
645 | def receive(self): | |||
|
646 | """ receive the next PDU from the watchman service | |||
|
647 | ||||
|
648 | If the client has activated subscriptions or logs then | |||
|
649 | this PDU may be a unilateral PDU sent by the service to | |||
|
650 | inform the client of a log event or subscription change. | |||
|
651 | ||||
|
652 | It may also simply be the response portion of a request | |||
|
653 | initiated by query. | |||
|
654 | ||||
|
655 | There are clients in production that subscribe and call | |||
|
656 | this in a loop to retrieve all subscription responses, | |||
|
657 | so care should be taken when making changes here. | |||
|
658 | """ | |||
|
659 | ||||
|
660 | self._connect() | |||
|
661 | result = self.recvConn.receive() | |||
|
662 | if self._hasprop(result, 'error'): | |||
|
663 | raise CommandError(result['error']) | |||
|
664 | ||||
|
665 | if self._hasprop(result, 'log'): | |||
|
666 | self.logs.append(result['log']) | |||
|
667 | ||||
|
668 | if self._hasprop(result, 'subscription'): | |||
|
669 | sub = result['subscription'] | |||
|
670 | if not (sub in self.subs): | |||
|
671 | self.subs[sub] = [] | |||
|
672 | self.subs[sub].append(result) | |||
|
673 | ||||
|
674 | # also accumulate in {root,sub} keyed store | |||
|
675 | root = os.path.normcase(result['root']) | |||
|
676 | if not root in self.sub_by_root: | |||
|
677 | self.sub_by_root[root] = {} | |||
|
678 | if not sub in self.sub_by_root[root]: | |||
|
679 | self.sub_by_root[root][sub] = [] | |||
|
680 | self.sub_by_root[root][sub].append(result) | |||
|
681 | ||||
|
682 | return result | |||
|
683 | ||||
|
684 | def isUnilateralResponse(self, res): | |||
|
685 | for k in self.unilateral: | |||
|
686 | if k in res: | |||
|
687 | return True | |||
|
688 | return False | |||
|
689 | ||||
|
690 | def getLog(self, remove=True): | |||
|
691 | """ Retrieve buffered log data | |||
|
692 | ||||
|
693 | If remove is true the data will be removed from the buffer. | |||
|
694 | Otherwise it will be left in the buffer | |||
|
695 | """ | |||
|
696 | res = self.logs | |||
|
697 | if remove: | |||
|
698 | self.logs = [] | |||
|
699 | return res | |||
|
700 | ||||
|
701 | def getSubscription(self, name, remove=True, root=None): | |||
|
702 | """ Retrieve the data associated with a named subscription | |||
|
703 | ||||
|
704 | If remove is True (the default), the subscription data is removed | |||
|
705 | from the buffer. Otherwise the data is returned but left in | |||
|
706 | the buffer. | |||
|
707 | ||||
|
708 | Returns None if there is no data associated with `name` | |||
|
709 | ||||
|
710 | If root is not None, then only return the subscription | |||
|
711 | data that matches both root and name. When used in this way, | |||
|
712 | remove processing impacts both the unscoped and scoped stores | |||
|
713 | for the subscription data. | |||
|
714 | """ | |||
|
715 | ||||
|
716 | if root is not None: | |||
|
717 | if not root in self.sub_by_root: | |||
|
718 | return None | |||
|
719 | if not name in self.sub_by_root[root]: | |||
|
720 | return None | |||
|
721 | sub = self.sub_by_root[root][name] | |||
|
722 | if remove: | |||
|
723 | del self.sub_by_root[root][name] | |||
|
724 | # don't let this grow unbounded | |||
|
725 | if name in self.subs: | |||
|
726 | del self.subs[name] | |||
|
727 | return sub | |||
|
728 | ||||
|
729 | if not (name in self.subs): | |||
|
730 | return None | |||
|
731 | sub = self.subs[name] | |||
|
732 | if remove: | |||
|
733 | del self.subs[name] | |||
|
734 | return sub | |||
|
735 | ||||
|
736 | def query(self, *args): | |||
|
737 | """ Send a query to the watchman service and return the response | |||
|
738 | ||||
|
739 | This call will block until the response is returned. | |||
|
740 | If any unilateral responses are sent by the service in between | |||
|
741 | the request-response they will be buffered up in the client object | |||
|
742 | and NOT returned via this method. | |||
|
743 | """ | |||
|
744 | ||||
|
745 | log('calling client.query') | |||
|
746 | self._connect() | |||
|
747 | try: | |||
|
748 | self.sendConn.send(args) | |||
|
749 | ||||
|
750 | res = self.receive() | |||
|
751 | while self.isUnilateralResponse(res): | |||
|
752 | res = self.receive() | |||
|
753 | ||||
|
754 | return res | |||
|
755 | except CommandError as ex: | |||
|
756 | ex.setCommand(args) | |||
|
757 | raise ex | |||
|
758 | ||||
|
759 | def capabilityCheck(self, optional=None, required=None): | |||
|
760 | """ Perform a server capability check """ | |||
|
761 | res = self.query('version', { | |||
|
762 | 'optional': optional or [], | |||
|
763 | 'required': required or [] | |||
|
764 | }) | |||
|
765 | ||||
|
766 | if not self._hasprop(res, 'capabilities'): | |||
|
767 | # Server doesn't support capabilities, so we need to | |||
|
768 | # synthesize the results based on the version | |||
|
769 | capabilities.synthesize(res, optional) | |||
|
770 | if 'error' in res: | |||
|
771 | raise CommandError(res['error']) | |||
|
772 | ||||
|
773 | return res | |||
|
774 | ||||
|
775 | def setTimeout(self, value): | |||
|
776 | self.recvConn.setTimeout(value) | |||
|
777 | self.sendConn.setTimeout(value) | |||
|
778 | ||||
|
779 | # no-check-code -- this is a 3rd party library |
This diff has been collapsed as it changes many lines, (950 lines changed) Show them Hide them | |||||
@@ -0,0 +1,950 b'' | |||||
|
1 | /* | |||
|
2 | Copyright (c) 2013-2015, Facebook, Inc. | |||
|
3 | All rights reserved. | |||
|
4 | ||||
|
5 | Redistribution and use in source and binary forms, with or without | |||
|
6 | modification, are permitted provided that the following conditions are met: | |||
|
7 | ||||
|
8 | * Redistributions of source code must retain the above copyright notice, | |||
|
9 | this list of conditions and the following disclaimer. | |||
|
10 | ||||
|
11 | * Redistributions in binary form must reproduce the above copyright notice, | |||
|
12 | this list of conditions and the following disclaimer in the documentation | |||
|
13 | and/or other materials provided with the distribution. | |||
|
14 | ||||
|
15 | * Neither the name Facebook nor the names of its contributors may be used to | |||
|
16 | endorse or promote products derived from this software without specific | |||
|
17 | prior written permission. | |||
|
18 | ||||
|
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
|
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
|
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
|
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
|
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
|
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
|
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
|
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
|
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
|
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
|
29 | */ | |||
|
30 | ||||
|
31 | #include <Python.h> | |||
|
32 | #ifdef _MSC_VER | |||
|
33 | #define inline __inline | |||
|
34 | #include <stdint.h> | |||
|
35 | #endif | |||
|
36 | ||||
|
37 | /* Return the smallest size int that can store the value */ | |||
|
38 | #define INT_SIZE(x) (((x) == ((int8_t)x)) ? 1 : \ | |||
|
39 | ((x) == ((int16_t)x)) ? 2 : \ | |||
|
40 | ((x) == ((int32_t)x)) ? 4 : 8) | |||
|
41 | ||||
|
42 | #define BSER_ARRAY 0x00 | |||
|
43 | #define BSER_OBJECT 0x01 | |||
|
44 | #define BSER_STRING 0x02 | |||
|
45 | #define BSER_INT8 0x03 | |||
|
46 | #define BSER_INT16 0x04 | |||
|
47 | #define BSER_INT32 0x05 | |||
|
48 | #define BSER_INT64 0x06 | |||
|
49 | #define BSER_REAL 0x07 | |||
|
50 | #define BSER_TRUE 0x08 | |||
|
51 | #define BSER_FALSE 0x09 | |||
|
52 | #define BSER_NULL 0x0a | |||
|
53 | #define BSER_TEMPLATE 0x0b | |||
|
54 | #define BSER_SKIP 0x0c | |||
|
55 | ||||
|
56 | // An immutable object representation of BSER_OBJECT. | |||
|
57 | // Rather than build a hash table, key -> value are obtained | |||
|
58 | // by walking the list of keys to determine the offset into | |||
|
59 | // the values array. The assumption is that the number of | |||
|
60 | // array elements will be typically small (~6 for the top | |||
|
61 | // level query result and typically 3-5 for the file entries) | |||
|
62 | // so that the time overhead for this is small compared to | |||
|
63 | // using a proper hash table. Even with this simplistic | |||
|
64 | // approach, this is still faster for the mercurial use case | |||
|
65 | // as it helps to eliminate creating N other objects to | |||
|
66 | // represent the stat information in the hgwatchman extension | |||
|
67 | typedef struct { | |||
|
68 | PyObject_HEAD | |||
|
69 | PyObject *keys; // tuple of field names | |||
|
70 | PyObject *values; // tuple of values | |||
|
71 | } bserObject; | |||
|
72 | ||||
|
73 | static Py_ssize_t bserobj_tuple_length(PyObject *o) { | |||
|
74 | bserObject *obj = (bserObject*)o; | |||
|
75 | ||||
|
76 | return PySequence_Length(obj->keys); | |||
|
77 | } | |||
|
78 | ||||
|
79 | static PyObject *bserobj_tuple_item(PyObject *o, Py_ssize_t i) { | |||
|
80 | bserObject *obj = (bserObject*)o; | |||
|
81 | ||||
|
82 | return PySequence_GetItem(obj->values, i); | |||
|
83 | } | |||
|
84 | ||||
|
85 | static PySequenceMethods bserobj_sq = { | |||
|
86 | bserobj_tuple_length, /* sq_length */ | |||
|
87 | 0, /* sq_concat */ | |||
|
88 | 0, /* sq_repeat */ | |||
|
89 | bserobj_tuple_item, /* sq_item */ | |||
|
90 | 0, /* sq_ass_item */ | |||
|
91 | 0, /* sq_contains */ | |||
|
92 | 0, /* sq_inplace_concat */ | |||
|
93 | 0 /* sq_inplace_repeat */ | |||
|
94 | }; | |||
|
95 | ||||
|
96 | static void bserobj_dealloc(PyObject *o) { | |||
|
97 | bserObject *obj = (bserObject*)o; | |||
|
98 | ||||
|
99 | Py_CLEAR(obj->keys); | |||
|
100 | Py_CLEAR(obj->values); | |||
|
101 | PyObject_Del(o); | |||
|
102 | } | |||
|
103 | ||||
|
104 | static PyObject *bserobj_getattrro(PyObject *o, PyObject *name) { | |||
|
105 | bserObject *obj = (bserObject*)o; | |||
|
106 | Py_ssize_t i, n; | |||
|
107 | const char *namestr; | |||
|
108 | ||||
|
109 | if (PyIndex_Check(name)) { | |||
|
110 | i = PyNumber_AsSsize_t(name, PyExc_IndexError); | |||
|
111 | if (i == -1 && PyErr_Occurred()) { | |||
|
112 | return NULL; | |||
|
113 | } | |||
|
114 | return PySequence_GetItem(obj->values, i); | |||
|
115 | } | |||
|
116 | ||||
|
117 | // hack^Wfeature to allow mercurial to use "st_size" to reference "size" | |||
|
118 | namestr = PyString_AsString(name); | |||
|
119 | if (!strncmp(namestr, "st_", 3)) { | |||
|
120 | namestr += 3; | |||
|
121 | } | |||
|
122 | ||||
|
123 | n = PyTuple_GET_SIZE(obj->keys); | |||
|
124 | for (i = 0; i < n; i++) { | |||
|
125 | const char *item_name = NULL; | |||
|
126 | PyObject *key = PyTuple_GET_ITEM(obj->keys, i); | |||
|
127 | ||||
|
128 | item_name = PyString_AsString(key); | |||
|
129 | if (!strcmp(item_name, namestr)) { | |||
|
130 | return PySequence_GetItem(obj->values, i); | |||
|
131 | } | |||
|
132 | } | |||
|
133 | PyErr_Format(PyExc_AttributeError, | |||
|
134 | "bserobject has no attribute '%.400s'", namestr); | |||
|
135 | return NULL; | |||
|
136 | } | |||
|
137 | ||||
|
138 | static PyMappingMethods bserobj_map = { | |||
|
139 | bserobj_tuple_length, /* mp_length */ | |||
|
140 | bserobj_getattrro, /* mp_subscript */ | |||
|
141 | 0 /* mp_ass_subscript */ | |||
|
142 | }; | |||
|
143 | ||||
|
144 | PyTypeObject bserObjectType = { | |||
|
145 | PyVarObject_HEAD_INIT(NULL, 0) | |||
|
146 | "bserobj_tuple", /* tp_name */ | |||
|
147 | sizeof(bserObject), /* tp_basicsize */ | |||
|
148 | 0, /* tp_itemsize */ | |||
|
149 | bserobj_dealloc, /* tp_dealloc */ | |||
|
150 | 0, /* tp_print */ | |||
|
151 | 0, /* tp_getattr */ | |||
|
152 | 0, /* tp_setattr */ | |||
|
153 | 0, /* tp_compare */ | |||
|
154 | 0, /* tp_repr */ | |||
|
155 | 0, /* tp_as_number */ | |||
|
156 | &bserobj_sq, /* tp_as_sequence */ | |||
|
157 | &bserobj_map, /* tp_as_mapping */ | |||
|
158 | 0, /* tp_hash */ | |||
|
159 | 0, /* tp_call */ | |||
|
160 | 0, /* tp_str */ | |||
|
161 | bserobj_getattrro, /* tp_getattro */ | |||
|
162 | 0, /* tp_setattro */ | |||
|
163 | 0, /* tp_as_buffer */ | |||
|
164 | Py_TPFLAGS_DEFAULT, /* tp_flags */ | |||
|
165 | "bserobj tuple", /* tp_doc */ | |||
|
166 | 0, /* tp_traverse */ | |||
|
167 | 0, /* tp_clear */ | |||
|
168 | 0, /* tp_richcompare */ | |||
|
169 | 0, /* tp_weaklistoffset */ | |||
|
170 | 0, /* tp_iter */ | |||
|
171 | 0, /* tp_iternext */ | |||
|
172 | 0, /* tp_methods */ | |||
|
173 | 0, /* tp_members */ | |||
|
174 | 0, /* tp_getset */ | |||
|
175 | 0, /* tp_base */ | |||
|
176 | 0, /* tp_dict */ | |||
|
177 | 0, /* tp_descr_get */ | |||
|
178 | 0, /* tp_descr_set */ | |||
|
179 | 0, /* tp_dictoffset */ | |||
|
180 | 0, /* tp_init */ | |||
|
181 | 0, /* tp_alloc */ | |||
|
182 | 0, /* tp_new */ | |||
|
183 | }; | |||
|
184 | ||||
|
185 | ||||
|
186 | static PyObject *bser_loads_recursive(const char **ptr, const char *end, | |||
|
187 | int mutable); | |||
|
188 | ||||
|
189 | static const char bser_true = BSER_TRUE; | |||
|
190 | static const char bser_false = BSER_FALSE; | |||
|
191 | static const char bser_null = BSER_NULL; | |||
|
192 | static const char bser_string_hdr = BSER_STRING; | |||
|
193 | static const char bser_array_hdr = BSER_ARRAY; | |||
|
194 | static const char bser_object_hdr = BSER_OBJECT; | |||
|
195 | ||||
|
196 | static inline uint32_t next_power_2(uint32_t n) | |||
|
197 | { | |||
|
198 | n |= (n >> 16); | |||
|
199 | n |= (n >> 8); | |||
|
200 | n |= (n >> 4); | |||
|
201 | n |= (n >> 2); | |||
|
202 | n |= (n >> 1); | |||
|
203 | return n + 1; | |||
|
204 | } | |||
|
205 | ||||
|
206 | // A buffer we use for building up the serialized result | |||
|
207 | struct bser_buffer { | |||
|
208 | char *buf; | |||
|
209 | int wpos, allocd; | |||
|
210 | }; | |||
|
211 | typedef struct bser_buffer bser_t; | |||
|
212 | ||||
|
213 | static int bser_append(bser_t *bser, const char *data, uint32_t len) | |||
|
214 | { | |||
|
215 | int newlen = next_power_2(bser->wpos + len); | |||
|
216 | if (newlen > bser->allocd) { | |||
|
217 | char *nbuf = realloc(bser->buf, newlen); | |||
|
218 | if (!nbuf) { | |||
|
219 | return 0; | |||
|
220 | } | |||
|
221 | ||||
|
222 | bser->buf = nbuf; | |||
|
223 | bser->allocd = newlen; | |||
|
224 | } | |||
|
225 | ||||
|
226 | memcpy(bser->buf + bser->wpos, data, len); | |||
|
227 | bser->wpos += len; | |||
|
228 | return 1; | |||
|
229 | } | |||
|
230 | ||||
|
231 | static int bser_init(bser_t *bser) | |||
|
232 | { | |||
|
233 | bser->allocd = 8192; | |||
|
234 | bser->wpos = 0; | |||
|
235 | bser->buf = malloc(bser->allocd); | |||
|
236 | ||||
|
237 | if (!bser->buf) { | |||
|
238 | return 0; | |||
|
239 | } | |||
|
240 | ||||
|
241 | // Leave room for the serialization header, which includes | |||
|
242 | // our overall length. To make things simpler, we'll use an | |||
|
243 | // int32 for the header | |||
|
244 | #define EMPTY_HEADER "\x00\x01\x05\x00\x00\x00\x00" | |||
|
245 | bser_append(bser, EMPTY_HEADER, sizeof(EMPTY_HEADER)-1); | |||
|
246 | ||||
|
247 | return 1; | |||
|
248 | } | |||
|
249 | ||||
|
250 | static void bser_dtor(bser_t *bser) | |||
|
251 | { | |||
|
252 | free(bser->buf); | |||
|
253 | bser->buf = NULL; | |||
|
254 | } | |||
|
255 | ||||
|
256 | static int bser_long(bser_t *bser, int64_t val) | |||
|
257 | { | |||
|
258 | int8_t i8; | |||
|
259 | int16_t i16; | |||
|
260 | int32_t i32; | |||
|
261 | int64_t i64; | |||
|
262 | char sz; | |||
|
263 | int size = INT_SIZE(val); | |||
|
264 | char *iptr; | |||
|
265 | ||||
|
266 | switch (size) { | |||
|
267 | case 1: | |||
|
268 | sz = BSER_INT8; | |||
|
269 | i8 = (int8_t)val; | |||
|
270 | iptr = (char*)&i8; | |||
|
271 | break; | |||
|
272 | case 2: | |||
|
273 | sz = BSER_INT16; | |||
|
274 | i16 = (int16_t)val; | |||
|
275 | iptr = (char*)&i16; | |||
|
276 | break; | |||
|
277 | case 4: | |||
|
278 | sz = BSER_INT32; | |||
|
279 | i32 = (int32_t)val; | |||
|
280 | iptr = (char*)&i32; | |||
|
281 | break; | |||
|
282 | case 8: | |||
|
283 | sz = BSER_INT64; | |||
|
284 | i64 = (int64_t)val; | |||
|
285 | iptr = (char*)&i64; | |||
|
286 | break; | |||
|
287 | default: | |||
|
288 | PyErr_SetString(PyExc_RuntimeError, | |||
|
289 | "Cannot represent this long value!?"); | |||
|
290 | return 0; | |||
|
291 | } | |||
|
292 | ||||
|
293 | if (!bser_append(bser, &sz, sizeof(sz))) { | |||
|
294 | return 0; | |||
|
295 | } | |||
|
296 | ||||
|
297 | return bser_append(bser, iptr, size); | |||
|
298 | } | |||
|
299 | ||||
|
300 | static int bser_string(bser_t *bser, PyObject *sval) | |||
|
301 | { | |||
|
302 | char *buf = NULL; | |||
|
303 | Py_ssize_t len; | |||
|
304 | int res; | |||
|
305 | PyObject *utf = NULL; | |||
|
306 | ||||
|
307 | if (PyUnicode_Check(sval)) { | |||
|
308 | utf = PyUnicode_AsEncodedString(sval, "utf-8", "ignore"); | |||
|
309 | sval = utf; | |||
|
310 | } | |||
|
311 | ||||
|
312 | res = PyString_AsStringAndSize(sval, &buf, &len); | |||
|
313 | if (res == -1) { | |||
|
314 | res = 0; | |||
|
315 | goto out; | |||
|
316 | } | |||
|
317 | ||||
|
318 | if (!bser_append(bser, &bser_string_hdr, sizeof(bser_string_hdr))) { | |||
|
319 | res = 0; | |||
|
320 | goto out; | |||
|
321 | } | |||
|
322 | ||||
|
323 | if (!bser_long(bser, len)) { | |||
|
324 | res = 0; | |||
|
325 | goto out; | |||
|
326 | } | |||
|
327 | ||||
|
328 | if (len > UINT32_MAX) { | |||
|
329 | PyErr_Format(PyExc_ValueError, "string too big"); | |||
|
330 | res = 0; | |||
|
331 | goto out; | |||
|
332 | } | |||
|
333 | ||||
|
334 | res = bser_append(bser, buf, (uint32_t)len); | |||
|
335 | ||||
|
336 | out: | |||
|
337 | if (utf) { | |||
|
338 | Py_DECREF(utf); | |||
|
339 | } | |||
|
340 | ||||
|
341 | return res; | |||
|
342 | } | |||
|
343 | ||||
|
344 | static int bser_recursive(bser_t *bser, PyObject *val) | |||
|
345 | { | |||
|
346 | if (PyBool_Check(val)) { | |||
|
347 | if (val == Py_True) { | |||
|
348 | return bser_append(bser, &bser_true, sizeof(bser_true)); | |||
|
349 | } | |||
|
350 | return bser_append(bser, &bser_false, sizeof(bser_false)); | |||
|
351 | } | |||
|
352 | ||||
|
353 | if (val == Py_None) { | |||
|
354 | return bser_append(bser, &bser_null, sizeof(bser_null)); | |||
|
355 | } | |||
|
356 | ||||
|
357 | if (PyInt_Check(val)) { | |||
|
358 | return bser_long(bser, PyInt_AS_LONG(val)); | |||
|
359 | } | |||
|
360 | ||||
|
361 | if (PyLong_Check(val)) { | |||
|
362 | return bser_long(bser, PyLong_AsLongLong(val)); | |||
|
363 | } | |||
|
364 | ||||
|
365 | if (PyString_Check(val) || PyUnicode_Check(val)) { | |||
|
366 | return bser_string(bser, val); | |||
|
367 | } | |||
|
368 | ||||
|
369 | ||||
|
370 | if (PyFloat_Check(val)) { | |||
|
371 | double dval = PyFloat_AS_DOUBLE(val); | |||
|
372 | char sz = BSER_REAL; | |||
|
373 | ||||
|
374 | if (!bser_append(bser, &sz, sizeof(sz))) { | |||
|
375 | return 0; | |||
|
376 | } | |||
|
377 | ||||
|
378 | return bser_append(bser, (char*)&dval, sizeof(dval)); | |||
|
379 | } | |||
|
380 | ||||
|
381 | if (PyList_Check(val)) { | |||
|
382 | Py_ssize_t i, len = PyList_GET_SIZE(val); | |||
|
383 | ||||
|
384 | if (!bser_append(bser, &bser_array_hdr, sizeof(bser_array_hdr))) { | |||
|
385 | return 0; | |||
|
386 | } | |||
|
387 | ||||
|
388 | if (!bser_long(bser, len)) { | |||
|
389 | return 0; | |||
|
390 | } | |||
|
391 | ||||
|
392 | for (i = 0; i < len; i++) { | |||
|
393 | PyObject *ele = PyList_GET_ITEM(val, i); | |||
|
394 | ||||
|
395 | if (!bser_recursive(bser, ele)) { | |||
|
396 | return 0; | |||
|
397 | } | |||
|
398 | } | |||
|
399 | ||||
|
400 | return 1; | |||
|
401 | } | |||
|
402 | ||||
|
403 | if (PyTuple_Check(val)) { | |||
|
404 | Py_ssize_t i, len = PyTuple_GET_SIZE(val); | |||
|
405 | ||||
|
406 | if (!bser_append(bser, &bser_array_hdr, sizeof(bser_array_hdr))) { | |||
|
407 | return 0; | |||
|
408 | } | |||
|
409 | ||||
|
410 | if (!bser_long(bser, len)) { | |||
|
411 | return 0; | |||
|
412 | } | |||
|
413 | ||||
|
414 | for (i = 0; i < len; i++) { | |||
|
415 | PyObject *ele = PyTuple_GET_ITEM(val, i); | |||
|
416 | ||||
|
417 | if (!bser_recursive(bser, ele)) { | |||
|
418 | return 0; | |||
|
419 | } | |||
|
420 | } | |||
|
421 | ||||
|
422 | return 1; | |||
|
423 | } | |||
|
424 | ||||
|
425 | if (PyMapping_Check(val)) { | |||
|
426 | Py_ssize_t len = PyMapping_Length(val); | |||
|
427 | Py_ssize_t pos = 0; | |||
|
428 | PyObject *key, *ele; | |||
|
429 | ||||
|
430 | if (!bser_append(bser, &bser_object_hdr, sizeof(bser_object_hdr))) { | |||
|
431 | return 0; | |||
|
432 | } | |||
|
433 | ||||
|
434 | if (!bser_long(bser, len)) { | |||
|
435 | return 0; | |||
|
436 | } | |||
|
437 | ||||
|
438 | while (PyDict_Next(val, &pos, &key, &ele)) { | |||
|
439 | if (!bser_string(bser, key)) { | |||
|
440 | return 0; | |||
|
441 | } | |||
|
442 | if (!bser_recursive(bser, ele)) { | |||
|
443 | return 0; | |||
|
444 | } | |||
|
445 | } | |||
|
446 | ||||
|
447 | return 1; | |||
|
448 | } | |||
|
449 | ||||
|
450 | PyErr_SetString(PyExc_ValueError, "Unsupported value type"); | |||
|
451 | return 0; | |||
|
452 | } | |||
|
453 | ||||
|
454 | static PyObject *bser_dumps(PyObject *self, PyObject *args) | |||
|
455 | { | |||
|
456 | PyObject *val = NULL, *res; | |||
|
457 | bser_t bser; | |||
|
458 | uint32_t len; | |||
|
459 | ||||
|
460 | if (!PyArg_ParseTuple(args, "O", &val)) { | |||
|
461 | return NULL; | |||
|
462 | } | |||
|
463 | ||||
|
464 | if (!bser_init(&bser)) { | |||
|
465 | return PyErr_NoMemory(); | |||
|
466 | } | |||
|
467 | ||||
|
468 | if (!bser_recursive(&bser, val)) { | |||
|
469 | bser_dtor(&bser); | |||
|
470 | if (errno == ENOMEM) { | |||
|
471 | return PyErr_NoMemory(); | |||
|
472 | } | |||
|
473 | // otherwise, we've already set the error to something reasonable | |||
|
474 | return NULL; | |||
|
475 | } | |||
|
476 | ||||
|
477 | // Now fill in the overall length | |||
|
478 | len = bser.wpos - (sizeof(EMPTY_HEADER) - 1); | |||
|
479 | memcpy(bser.buf + 3, &len, sizeof(len)); | |||
|
480 | ||||
|
481 | res = PyString_FromStringAndSize(bser.buf, bser.wpos); | |||
|
482 | bser_dtor(&bser); | |||
|
483 | ||||
|
484 | return res; | |||
|
485 | } | |||
|
486 | ||||
|
487 | int bunser_int(const char **ptr, const char *end, int64_t *val) | |||
|
488 | { | |||
|
489 | int needed; | |||
|
490 | const char *buf = *ptr; | |||
|
491 | int8_t i8; | |||
|
492 | int16_t i16; | |||
|
493 | int32_t i32; | |||
|
494 | int64_t i64; | |||
|
495 | ||||
|
496 | switch (buf[0]) { | |||
|
497 | case BSER_INT8: | |||
|
498 | needed = 2; | |||
|
499 | break; | |||
|
500 | case BSER_INT16: | |||
|
501 | needed = 3; | |||
|
502 | break; | |||
|
503 | case BSER_INT32: | |||
|
504 | needed = 5; | |||
|
505 | break; | |||
|
506 | case BSER_INT64: | |||
|
507 | needed = 9; | |||
|
508 | break; | |||
|
509 | default: | |||
|
510 | PyErr_Format(PyExc_ValueError, | |||
|
511 | "invalid bser int encoding 0x%02x", buf[0]); | |||
|
512 | return 0; | |||
|
513 | } | |||
|
514 | if (end - buf < needed) { | |||
|
515 | PyErr_SetString(PyExc_ValueError, "input buffer to small for int encoding"); | |||
|
516 | return 0; | |||
|
517 | } | |||
|
518 | *ptr = buf + needed; | |||
|
519 | switch (buf[0]) { | |||
|
520 | case BSER_INT8: | |||
|
521 | memcpy(&i8, buf + 1, sizeof(i8)); | |||
|
522 | *val = i8; | |||
|
523 | return 1; | |||
|
524 | case BSER_INT16: | |||
|
525 | memcpy(&i16, buf + 1, sizeof(i16)); | |||
|
526 | *val = i16; | |||
|
527 | return 1; | |||
|
528 | case BSER_INT32: | |||
|
529 | memcpy(&i32, buf + 1, sizeof(i32)); | |||
|
530 | *val = i32; | |||
|
531 | return 1; | |||
|
532 | case BSER_INT64: | |||
|
533 | memcpy(&i64, buf + 1, sizeof(i64)); | |||
|
534 | *val = i64; | |||
|
535 | return 1; | |||
|
536 | default: | |||
|
537 | return 0; | |||
|
538 | } | |||
|
539 | } | |||
|
540 | ||||
|
541 | static int bunser_string(const char **ptr, const char *end, | |||
|
542 | const char **start, int64_t *len) | |||
|
543 | { | |||
|
544 | const char *buf = *ptr; | |||
|
545 | ||||
|
546 | // skip string marker | |||
|
547 | buf++; | |||
|
548 | if (!bunser_int(&buf, end, len)) { | |||
|
549 | return 0; | |||
|
550 | } | |||
|
551 | ||||
|
552 | if (buf + *len > end) { | |||
|
553 | PyErr_Format(PyExc_ValueError, "invalid string length in bser data"); | |||
|
554 | return 0; | |||
|
555 | } | |||
|
556 | ||||
|
557 | *ptr = buf + *len; | |||
|
558 | *start = buf; | |||
|
559 | return 1; | |||
|
560 | } | |||
|
561 | ||||
|
562 | static PyObject *bunser_array(const char **ptr, const char *end, int mutable) | |||
|
563 | { | |||
|
564 | const char *buf = *ptr; | |||
|
565 | int64_t nitems, i; | |||
|
566 | PyObject *res; | |||
|
567 | ||||
|
568 | // skip array header | |||
|
569 | buf++; | |||
|
570 | if (!bunser_int(&buf, end, &nitems)) { | |||
|
571 | return 0; | |||
|
572 | } | |||
|
573 | *ptr = buf; | |||
|
574 | ||||
|
575 | if (nitems > LONG_MAX) { | |||
|
576 | PyErr_Format(PyExc_ValueError, "too many items for python array"); | |||
|
577 | return NULL; | |||
|
578 | } | |||
|
579 | ||||
|
580 | if (mutable) { | |||
|
581 | res = PyList_New((Py_ssize_t)nitems); | |||
|
582 | } else { | |||
|
583 | res = PyTuple_New((Py_ssize_t)nitems); | |||
|
584 | } | |||
|
585 | ||||
|
586 | for (i = 0; i < nitems; i++) { | |||
|
587 | PyObject *ele = bser_loads_recursive(ptr, end, mutable); | |||
|
588 | ||||
|
589 | if (!ele) { | |||
|
590 | Py_DECREF(res); | |||
|
591 | return NULL; | |||
|
592 | } | |||
|
593 | ||||
|
594 | if (mutable) { | |||
|
595 | PyList_SET_ITEM(res, i, ele); | |||
|
596 | } else { | |||
|
597 | PyTuple_SET_ITEM(res, i, ele); | |||
|
598 | } | |||
|
599 | // DECREF(ele) not required as SET_ITEM steals the ref | |||
|
600 | } | |||
|
601 | ||||
|
602 | return res; | |||
|
603 | } | |||
|
604 | ||||
|
605 | static PyObject *bunser_object(const char **ptr, const char *end, | |||
|
606 | int mutable) | |||
|
607 | { | |||
|
608 | const char *buf = *ptr; | |||
|
609 | int64_t nitems, i; | |||
|
610 | PyObject *res; | |||
|
611 | bserObject *obj; | |||
|
612 | ||||
|
613 | // skip array header | |||
|
614 | buf++; | |||
|
615 | if (!bunser_int(&buf, end, &nitems)) { | |||
|
616 | return 0; | |||
|
617 | } | |||
|
618 | *ptr = buf; | |||
|
619 | ||||
|
620 | if (mutable) { | |||
|
621 | res = PyDict_New(); | |||
|
622 | } else { | |||
|
623 | obj = PyObject_New(bserObject, &bserObjectType); | |||
|
624 | obj->keys = PyTuple_New((Py_ssize_t)nitems); | |||
|
625 | obj->values = PyTuple_New((Py_ssize_t)nitems); | |||
|
626 | res = (PyObject*)obj; | |||
|
627 | } | |||
|
628 | ||||
|
629 | for (i = 0; i < nitems; i++) { | |||
|
630 | const char *keystr; | |||
|
631 | int64_t keylen; | |||
|
632 | PyObject *key; | |||
|
633 | PyObject *ele; | |||
|
634 | ||||
|
635 | if (!bunser_string(ptr, end, &keystr, &keylen)) { | |||
|
636 | Py_DECREF(res); | |||
|
637 | return NULL; | |||
|
638 | } | |||
|
639 | ||||
|
640 | if (keylen > LONG_MAX) { | |||
|
641 | PyErr_Format(PyExc_ValueError, "string too big for python"); | |||
|
642 | Py_DECREF(res); | |||
|
643 | return NULL; | |||
|
644 | } | |||
|
645 | ||||
|
646 | key = PyString_FromStringAndSize(keystr, (Py_ssize_t)keylen); | |||
|
647 | if (!key) { | |||
|
648 | Py_DECREF(res); | |||
|
649 | return NULL; | |||
|
650 | } | |||
|
651 | ||||
|
652 | ele = bser_loads_recursive(ptr, end, mutable); | |||
|
653 | ||||
|
654 | if (!ele) { | |||
|
655 | Py_DECREF(key); | |||
|
656 | Py_DECREF(res); | |||
|
657 | return NULL; | |||
|
658 | } | |||
|
659 | ||||
|
660 | if (mutable) { | |||
|
661 | PyDict_SetItem(res, key, ele); | |||
|
662 | Py_DECREF(key); | |||
|
663 | Py_DECREF(ele); | |||
|
664 | } else { | |||
|
665 | /* PyTuple_SET_ITEM steals ele, key */ | |||
|
666 | PyTuple_SET_ITEM(obj->values, i, ele); | |||
|
667 | PyTuple_SET_ITEM(obj->keys, i, key); | |||
|
668 | } | |||
|
669 | } | |||
|
670 | ||||
|
671 | return res; | |||
|
672 | } | |||
|
673 | ||||
|
674 | static PyObject *bunser_template(const char **ptr, const char *end, | |||
|
675 | int mutable) | |||
|
676 | { | |||
|
677 | const char *buf = *ptr; | |||
|
678 | int64_t nitems, i; | |||
|
679 | PyObject *arrval; | |||
|
680 | PyObject *keys; | |||
|
681 | Py_ssize_t numkeys, keyidx; | |||
|
682 | ||||
|
683 | if (buf[1] != BSER_ARRAY) { | |||
|
684 | PyErr_Format(PyExc_ValueError, "Expect ARRAY to follow TEMPLATE"); | |||
|
685 | return NULL; | |||
|
686 | } | |||
|
687 | ||||
|
688 | // skip header | |||
|
689 | buf++; | |||
|
690 | *ptr = buf; | |||
|
691 | ||||
|
692 | // Load template keys | |||
|
693 | keys = bunser_array(ptr, end, mutable); | |||
|
694 | if (!keys) { | |||
|
695 | return NULL; | |||
|
696 | } | |||
|
697 | ||||
|
698 | numkeys = PySequence_Length(keys); | |||
|
699 | ||||
|
700 | // Load number of array elements | |||
|
701 | if (!bunser_int(ptr, end, &nitems)) { | |||
|
702 | Py_DECREF(keys); | |||
|
703 | return 0; | |||
|
704 | } | |||
|
705 | ||||
|
706 | if (nitems > LONG_MAX) { | |||
|
707 | PyErr_Format(PyExc_ValueError, "Too many items for python"); | |||
|
708 | Py_DECREF(keys); | |||
|
709 | return NULL; | |||
|
710 | } | |||
|
711 | ||||
|
712 | arrval = PyList_New((Py_ssize_t)nitems); | |||
|
713 | if (!arrval) { | |||
|
714 | Py_DECREF(keys); | |||
|
715 | return NULL; | |||
|
716 | } | |||
|
717 | ||||
|
718 | for (i = 0; i < nitems; i++) { | |||
|
719 | PyObject *dict = NULL; | |||
|
720 | bserObject *obj = NULL; | |||
|
721 | ||||
|
722 | if (mutable) { | |||
|
723 | dict = PyDict_New(); | |||
|
724 | } else { | |||
|
725 | obj = PyObject_New(bserObject, &bserObjectType); | |||
|
726 | if (obj) { | |||
|
727 | obj->keys = keys; | |||
|
728 | Py_INCREF(obj->keys); | |||
|
729 | obj->values = PyTuple_New(numkeys); | |||
|
730 | } | |||
|
731 | dict = (PyObject*)obj; | |||
|
732 | } | |||
|
733 | if (!dict) { | |||
|
734 | fail: | |||
|
735 | Py_DECREF(keys); | |||
|
736 | Py_DECREF(arrval); | |||
|
737 | return NULL; | |||
|
738 | } | |||
|
739 | ||||
|
740 | for (keyidx = 0; keyidx < numkeys; keyidx++) { | |||
|
741 | PyObject *key; | |||
|
742 | PyObject *ele; | |||
|
743 | ||||
|
744 | if (**ptr == BSER_SKIP) { | |||
|
745 | *ptr = *ptr + 1; | |||
|
746 | ele = Py_None; | |||
|
747 | Py_INCREF(ele); | |||
|
748 | } else { | |||
|
749 | ele = bser_loads_recursive(ptr, end, mutable); | |||
|
750 | } | |||
|
751 | ||||
|
752 | if (!ele) { | |||
|
753 | goto fail; | |||
|
754 | } | |||
|
755 | ||||
|
756 | if (mutable) { | |||
|
757 | key = PyList_GET_ITEM(keys, keyidx); | |||
|
758 | PyDict_SetItem(dict, key, ele); | |||
|
759 | Py_DECREF(ele); | |||
|
760 | } else { | |||
|
761 | PyTuple_SET_ITEM(obj->values, keyidx, ele); | |||
|
762 | // DECREF(ele) not required as SET_ITEM steals the ref | |||
|
763 | } | |||
|
764 | } | |||
|
765 | ||||
|
766 | PyList_SET_ITEM(arrval, i, dict); | |||
|
767 | // DECREF(obj) not required as SET_ITEM steals the ref | |||
|
768 | } | |||
|
769 | ||||
|
770 | Py_DECREF(keys); | |||
|
771 | ||||
|
772 | return arrval; | |||
|
773 | } | |||
|
774 | ||||
|
775 | static PyObject *bser_loads_recursive(const char **ptr, const char *end, | |||
|
776 | int mutable) | |||
|
777 | { | |||
|
778 | const char *buf = *ptr; | |||
|
779 | ||||
|
780 | switch (buf[0]) { | |||
|
781 | case BSER_INT8: | |||
|
782 | case BSER_INT16: | |||
|
783 | case BSER_INT32: | |||
|
784 | case BSER_INT64: | |||
|
785 | { | |||
|
786 | int64_t ival; | |||
|
787 | if (!bunser_int(ptr, end, &ival)) { | |||
|
788 | return NULL; | |||
|
789 | } | |||
|
790 | if (ival < LONG_MIN || ival > LONG_MAX) { | |||
|
791 | return PyLong_FromLongLong(ival); | |||
|
792 | } | |||
|
793 | return PyInt_FromSsize_t(Py_SAFE_DOWNCAST(ival, int64_t, Py_ssize_t)); | |||
|
794 | } | |||
|
795 | ||||
|
796 | case BSER_REAL: | |||
|
797 | { | |||
|
798 | double dval; | |||
|
799 | memcpy(&dval, buf + 1, sizeof(dval)); | |||
|
800 | *ptr = buf + 1 + sizeof(double); | |||
|
801 | return PyFloat_FromDouble(dval); | |||
|
802 | } | |||
|
803 | ||||
|
804 | case BSER_TRUE: | |||
|
805 | *ptr = buf + 1; | |||
|
806 | Py_INCREF(Py_True); | |||
|
807 | return Py_True; | |||
|
808 | ||||
|
809 | case BSER_FALSE: | |||
|
810 | *ptr = buf + 1; | |||
|
811 | Py_INCREF(Py_False); | |||
|
812 | return Py_False; | |||
|
813 | ||||
|
814 | case BSER_NULL: | |||
|
815 | *ptr = buf + 1; | |||
|
816 | Py_INCREF(Py_None); | |||
|
817 | return Py_None; | |||
|
818 | ||||
|
819 | case BSER_STRING: | |||
|
820 | { | |||
|
821 | const char *start; | |||
|
822 | int64_t len; | |||
|
823 | ||||
|
824 | if (!bunser_string(ptr, end, &start, &len)) { | |||
|
825 | return NULL; | |||
|
826 | } | |||
|
827 | ||||
|
828 | if (len > LONG_MAX) { | |||
|
829 | PyErr_Format(PyExc_ValueError, "string too long for python"); | |||
|
830 | return NULL; | |||
|
831 | } | |||
|
832 | ||||
|
833 | return PyString_FromStringAndSize(start, (long)len); | |||
|
834 | } | |||
|
835 | ||||
|
836 | case BSER_ARRAY: | |||
|
837 | return bunser_array(ptr, end, mutable); | |||
|
838 | ||||
|
839 | case BSER_OBJECT: | |||
|
840 | return bunser_object(ptr, end, mutable); | |||
|
841 | ||||
|
842 | case BSER_TEMPLATE: | |||
|
843 | return bunser_template(ptr, end, mutable); | |||
|
844 | ||||
|
845 | default: | |||
|
846 | PyErr_Format(PyExc_ValueError, "unhandled bser opcode 0x%02x", buf[0]); | |||
|
847 | } | |||
|
848 | ||||
|
849 | return NULL; | |||
|
850 | } | |||
|
851 | ||||
|
852 | // Expected use case is to read a packet from the socket and | |||
|
853 | // then call bser.pdu_len on the packet. It returns the total | |||
|
854 | // length of the entire response that the peer is sending, | |||
|
855 | // including the bytes already received. This allows the client | |||
|
856 | // to compute the data size it needs to read before it can | |||
|
857 | // decode the data | |||
|
858 | static PyObject *bser_pdu_len(PyObject *self, PyObject *args) | |||
|
859 | { | |||
|
860 | const char *start = NULL; | |||
|
861 | const char *data = NULL; | |||
|
862 | int datalen = 0; | |||
|
863 | const char *end; | |||
|
864 | int64_t expected_len, total_len; | |||
|
865 | ||||
|
866 | if (!PyArg_ParseTuple(args, "s#", &start, &datalen)) { | |||
|
867 | return NULL; | |||
|
868 | } | |||
|
869 | data = start; | |||
|
870 | end = data + datalen; | |||
|
871 | ||||
|
872 | // Validate the header and length | |||
|
873 | if (memcmp(data, EMPTY_HEADER, 2) != 0) { | |||
|
874 | PyErr_SetString(PyExc_ValueError, "invalid bser header"); | |||
|
875 | return NULL; | |||
|
876 | } | |||
|
877 | ||||
|
878 | data += 2; | |||
|
879 | ||||
|
880 | // Expect an integer telling us how big the rest of the data | |||
|
881 | // should be | |||
|
882 | if (!bunser_int(&data, end, &expected_len)) { | |||
|
883 | return NULL; | |||
|
884 | } | |||
|
885 | ||||
|
886 | total_len = expected_len + (data - start); | |||
|
887 | if (total_len > LONG_MAX) { | |||
|
888 | return PyLong_FromLongLong(total_len); | |||
|
889 | } | |||
|
890 | return PyInt_FromLong((long)total_len); | |||
|
891 | } | |||
|
892 | ||||
|
893 | static PyObject *bser_loads(PyObject *self, PyObject *args) | |||
|
894 | { | |||
|
895 | const char *data = NULL; | |||
|
896 | int datalen = 0; | |||
|
897 | const char *end; | |||
|
898 | int64_t expected_len; | |||
|
899 | int mutable = 1; | |||
|
900 | PyObject *mutable_obj = NULL; | |||
|
901 | ||||
|
902 | if (!PyArg_ParseTuple(args, "s#|O:loads", &data, &datalen, &mutable_obj)) { | |||
|
903 | return NULL; | |||
|
904 | } | |||
|
905 | if (mutable_obj) { | |||
|
906 | mutable = PyObject_IsTrue(mutable_obj) > 0 ? 1 : 0; | |||
|
907 | } | |||
|
908 | ||||
|
909 | end = data + datalen; | |||
|
910 | ||||
|
911 | // Validate the header and length | |||
|
912 | if (memcmp(data, EMPTY_HEADER, 2) != 0) { | |||
|
913 | PyErr_SetString(PyExc_ValueError, "invalid bser header"); | |||
|
914 | return NULL; | |||
|
915 | } | |||
|
916 | ||||
|
917 | data += 2; | |||
|
918 | ||||
|
919 | // Expect an integer telling us how big the rest of the data | |||
|
920 | // should be | |||
|
921 | if (!bunser_int(&data, end, &expected_len)) { | |||
|
922 | return NULL; | |||
|
923 | } | |||
|
924 | ||||
|
925 | // Verify | |||
|
926 | if (expected_len + data != end) { | |||
|
927 | PyErr_SetString(PyExc_ValueError, "bser data len != header len"); | |||
|
928 | return NULL; | |||
|
929 | } | |||
|
930 | ||||
|
931 | return bser_loads_recursive(&data, end, mutable); | |||
|
932 | } | |||
|
933 | ||||
|
934 | static PyMethodDef bser_methods[] = { | |||
|
935 | {"loads", bser_loads, METH_VARARGS, "Deserialize string."}, | |||
|
936 | {"pdu_len", bser_pdu_len, METH_VARARGS, "Extract PDU length."}, | |||
|
937 | {"dumps", bser_dumps, METH_VARARGS, "Serialize string."}, | |||
|
938 | {NULL, NULL, 0, NULL} | |||
|
939 | }; | |||
|
940 | ||||
|
941 | PyMODINIT_FUNC initbser(void) | |||
|
942 | { | |||
|
943 | (void)Py_InitModule("bser", bser_methods); | |||
|
944 | PyType_Ready(&bserObjectType); | |||
|
945 | } | |||
|
946 | ||||
|
947 | /* vim:ts=2:sw=2:et: | |||
|
948 | */ | |||
|
949 | ||||
|
950 | // no-check-code -- this is a 3rd party library |
@@ -0,0 +1,69 b'' | |||||
|
1 | # Copyright 2015 Facebook, Inc. | |||
|
2 | # All rights reserved. | |||
|
3 | # | |||
|
4 | # Redistribution and use in source and binary forms, with or without | |||
|
5 | # modification, are permitted provided that the following conditions are met: | |||
|
6 | # | |||
|
7 | # * Redistributions of source code must retain the above copyright notice, | |||
|
8 | # this list of conditions and the following disclaimer. | |||
|
9 | # | |||
|
10 | # * Redistributions in binary form must reproduce the above copyright notice, | |||
|
11 | # this list of conditions and the following disclaimer in the documentation | |||
|
12 | # and/or other materials provided with the distribution. | |||
|
13 | # | |||
|
14 | # * Neither the name Facebook nor the names of its contributors may be used to | |||
|
15 | # endorse or promote products derived from this software without specific | |||
|
16 | # prior written permission. | |||
|
17 | # | |||
|
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
|
19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
|
20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
|
21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
|
22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
|
23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
|
24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
|
25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
|
26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
|
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
|
28 | ||||
|
29 | import re | |||
|
30 | ||||
|
31 | def parse_version(vstr): | |||
|
32 | res = 0 | |||
|
33 | for n in vstr.split('.'): | |||
|
34 | res = res * 1000 | |||
|
35 | res = res + int(n) | |||
|
36 | return res | |||
|
37 | ||||
|
38 | cap_versions = { | |||
|
39 | "cmd-watch-del-all": "3.1.1", | |||
|
40 | "cmd-watch-project": "3.1", | |||
|
41 | "relative_root": "3.3", | |||
|
42 | "term-dirname": "3.1", | |||
|
43 | "term-idirname": "3.1", | |||
|
44 | "wildmatch": "3.7", | |||
|
45 | } | |||
|
46 | ||||
|
47 | def check(version, name): | |||
|
48 | if name in cap_versions: | |||
|
49 | return version >= parse_version(cap_versions[name]) | |||
|
50 | return False | |||
|
51 | ||||
|
52 | def synthesize(vers, opts): | |||
|
53 | """ Synthesize a capability enabled version response | |||
|
54 | This is a very limited emulation for relatively recent feature sets | |||
|
55 | """ | |||
|
56 | parsed_version = parse_version(vers['version']) | |||
|
57 | vers['capabilities'] = {} | |||
|
58 | for name in opts['optional']: | |||
|
59 | vers['capabilities'][name] = check(parsed_version, name) | |||
|
60 | failed = False | |||
|
61 | for name in opts['required']: | |||
|
62 | have = check(parsed_version, name) | |||
|
63 | vers['capabilities'][name] = have | |||
|
64 | if not have: | |||
|
65 | vers['error'] = 'client required capability `' + name + \ | |||
|
66 | '` is not supported by this server' | |||
|
67 | return vers | |||
|
68 | ||||
|
69 | # no-check-code -- this is a 3rd party library |
@@ -0,0 +1,359 b'' | |||||
|
1 | # Copyright 2015 Facebook, Inc. | |||
|
2 | # All rights reserved. | |||
|
3 | # | |||
|
4 | # Redistribution and use in source and binary forms, with or without | |||
|
5 | # modification, are permitted provided that the following conditions are met: | |||
|
6 | # | |||
|
7 | # * Redistributions of source code must retain the above copyright notice, | |||
|
8 | # this list of conditions and the following disclaimer. | |||
|
9 | # | |||
|
10 | # * Redistributions in binary form must reproduce the above copyright notice, | |||
|
11 | # this list of conditions and the following disclaimer in the documentation | |||
|
12 | # and/or other materials provided with the distribution. | |||
|
13 | # | |||
|
14 | # * Neither the name Facebook nor the names of its contributors may be used to | |||
|
15 | # endorse or promote products derived from this software without specific | |||
|
16 | # prior written permission. | |||
|
17 | # | |||
|
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
|
19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
|
20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
|
21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
|
22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
|
23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
|
24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
|
25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
|
26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
|
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
|
28 | ||||
|
29 | import collections | |||
|
30 | import ctypes | |||
|
31 | import struct | |||
|
32 | import sys | |||
|
33 | ||||
|
34 | BSER_ARRAY = '\x00' | |||
|
35 | BSER_OBJECT = '\x01' | |||
|
36 | BSER_STRING = '\x02' | |||
|
37 | BSER_INT8 = '\x03' | |||
|
38 | BSER_INT16 = '\x04' | |||
|
39 | BSER_INT32 = '\x05' | |||
|
40 | BSER_INT64 = '\x06' | |||
|
41 | BSER_REAL = '\x07' | |||
|
42 | BSER_TRUE = '\x08' | |||
|
43 | BSER_FALSE = '\x09' | |||
|
44 | BSER_NULL = '\x0a' | |||
|
45 | BSER_TEMPLATE = '\x0b' | |||
|
46 | BSER_SKIP = '\x0c' | |||
|
47 | ||||
|
48 | # Leave room for the serialization header, which includes | |||
|
49 | # our overall length. To make things simpler, we'll use an | |||
|
50 | # int32 for the header | |||
|
51 | EMPTY_HEADER = "\x00\x01\x05\x00\x00\x00\x00" | |||
|
52 | ||||
|
53 | # Python 3 conditional for supporting Python 2's int/long types | |||
|
54 | if sys.version_info > (3,): | |||
|
55 | long = int | |||
|
56 | ||||
|
57 | def _int_size(x): | |||
|
58 | """Return the smallest size int that can store the value""" | |||
|
59 | if -0x80 <= x <= 0x7F: | |||
|
60 | return 1 | |||
|
61 | elif -0x8000 <= x <= 0x7FFF: | |||
|
62 | return 2 | |||
|
63 | elif -0x80000000 <= x <= 0x7FFFFFFF: | |||
|
64 | return 4 | |||
|
65 | elif long(-0x8000000000000000) <= x <= long(0x7FFFFFFFFFFFFFFF): | |||
|
66 | return 8 | |||
|
67 | else: | |||
|
68 | raise RuntimeError('Cannot represent value: ' + str(x)) | |||
|
69 | ||||
|
70 | ||||
|
71 | class _bser_buffer(object): | |||
|
72 | ||||
|
73 | def __init__(self): | |||
|
74 | self.buf = ctypes.create_string_buffer(8192) | |||
|
75 | struct.pack_into(str(len(EMPTY_HEADER)) + 's', self.buf, 0, EMPTY_HEADER) | |||
|
76 | self.wpos = len(EMPTY_HEADER) | |||
|
77 | ||||
|
78 | def ensure_size(self, size): | |||
|
79 | while ctypes.sizeof(self.buf) - self.wpos < size: | |||
|
80 | ctypes.resize(self.buf, ctypes.sizeof(self.buf) * 2) | |||
|
81 | ||||
|
82 | def append_long(self, val): | |||
|
83 | size = _int_size(val) | |||
|
84 | to_write = size + 1 | |||
|
85 | self.ensure_size(to_write) | |||
|
86 | if size == 1: | |||
|
87 | struct.pack_into('=cb', self.buf, self.wpos, BSER_INT8, val) | |||
|
88 | elif size == 2: | |||
|
89 | struct.pack_into('=ch', self.buf, self.wpos, BSER_INT16, val) | |||
|
90 | elif size == 4: | |||
|
91 | struct.pack_into('=ci', self.buf, self.wpos, BSER_INT32, val) | |||
|
92 | elif size == 8: | |||
|
93 | struct.pack_into('=cq', self.buf, self.wpos, BSER_INT64, val) | |||
|
94 | else: | |||
|
95 | raise RuntimeError('Cannot represent this long value') | |||
|
96 | self.wpos += to_write | |||
|
97 | ||||
|
98 | ||||
|
99 | def append_string(self, s): | |||
|
100 | if isinstance(s, unicode): | |||
|
101 | s = s.encode('utf-8') | |||
|
102 | s_len = len(s) | |||
|
103 | size = _int_size(s_len) | |||
|
104 | to_write = 2 + size + s_len | |||
|
105 | self.ensure_size(to_write) | |||
|
106 | if size == 1: | |||
|
107 | struct.pack_into('=ccb' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT8, s_len, s) | |||
|
108 | elif size == 2: | |||
|
109 | struct.pack_into('=cch' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT16, s_len, s) | |||
|
110 | elif size == 4: | |||
|
111 | struct.pack_into('=cci' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT32, s_len, s) | |||
|
112 | elif size == 8: | |||
|
113 | struct.pack_into('=ccq' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT64, s_len, s) | |||
|
114 | else: | |||
|
115 | raise RuntimeError('Cannot represent this string value') | |||
|
116 | self.wpos += to_write | |||
|
117 | ||||
|
118 | ||||
|
119 | def append_recursive(self, val): | |||
|
120 | if isinstance(val, bool): | |||
|
121 | needed = 1 | |||
|
122 | self.ensure_size(needed) | |||
|
123 | if val: | |||
|
124 | to_encode = BSER_TRUE | |||
|
125 | else: | |||
|
126 | to_encode = BSER_FALSE | |||
|
127 | struct.pack_into('=c', self.buf, self.wpos, to_encode) | |||
|
128 | self.wpos += needed | |||
|
129 | elif val is None: | |||
|
130 | needed = 1 | |||
|
131 | self.ensure_size(needed) | |||
|
132 | struct.pack_into('=c', self.buf, self.wpos, BSER_NULL) | |||
|
133 | self.wpos += needed | |||
|
134 | elif isinstance(val, (int, long)): | |||
|
135 | self.append_long(val) | |||
|
136 | elif isinstance(val, (str, unicode)): | |||
|
137 | self.append_string(val) | |||
|
138 | elif isinstance(val, float): | |||
|
139 | needed = 9 | |||
|
140 | self.ensure_size(needed) | |||
|
141 | struct.pack_into('=cd', self.buf, self.wpos, BSER_REAL, val) | |||
|
142 | self.wpos += needed | |||
|
143 | elif isinstance(val, collections.Mapping) and isinstance(val, collections.Sized): | |||
|
144 | val_len = len(val) | |||
|
145 | size = _int_size(val_len) | |||
|
146 | needed = 2 + size | |||
|
147 | self.ensure_size(needed) | |||
|
148 | if size == 1: | |||
|
149 | struct.pack_into('=ccb', self.buf, self.wpos, BSER_OBJECT, BSER_INT8, val_len) | |||
|
150 | elif size == 2: | |||
|
151 | struct.pack_into('=cch', self.buf, self.wpos, BSER_OBJECT, BSER_INT16, val_len) | |||
|
152 | elif size == 4: | |||
|
153 | struct.pack_into('=cci', self.buf, self.wpos, BSER_OBJECT, BSER_INT32, val_len) | |||
|
154 | elif size == 8: | |||
|
155 | struct.pack_into('=ccq', self.buf, self.wpos, BSER_OBJECT, BSER_INT64, val_len) | |||
|
156 | else: | |||
|
157 | raise RuntimeError('Cannot represent this mapping value') | |||
|
158 | self.wpos += needed | |||
|
159 | for k, v in val.iteritems(): | |||
|
160 | self.append_string(k) | |||
|
161 | self.append_recursive(v) | |||
|
162 | elif isinstance(val, collections.Iterable) and isinstance(val, collections.Sized): | |||
|
163 | val_len = len(val) | |||
|
164 | size = _int_size(val_len) | |||
|
165 | needed = 2 + size | |||
|
166 | self.ensure_size(needed) | |||
|
167 | if size == 1: | |||
|
168 | struct.pack_into('=ccb', self.buf, self.wpos, BSER_ARRAY, BSER_INT8, val_len) | |||
|
169 | elif size == 2: | |||
|
170 | struct.pack_into('=cch', self.buf, self.wpos, BSER_ARRAY, BSER_INT16, val_len) | |||
|
171 | elif size == 4: | |||
|
172 | struct.pack_into('=cci', self.buf, self.wpos, BSER_ARRAY, BSER_INT32, val_len) | |||
|
173 | elif size == 8: | |||
|
174 | struct.pack_into('=ccq', self.buf, self.wpos, BSER_ARRAY, BSER_INT64, val_len) | |||
|
175 | else: | |||
|
176 | raise RuntimeError('Cannot represent this sequence value') | |||
|
177 | self.wpos += needed | |||
|
178 | for v in val: | |||
|
179 | self.append_recursive(v) | |||
|
180 | else: | |||
|
181 | raise RuntimeError('Cannot represent unknown value type') | |||
|
182 | ||||
|
183 | ||||
|
184 | def dumps(obj): | |||
|
185 | bser_buf = _bser_buffer() | |||
|
186 | bser_buf.append_recursive(obj) | |||
|
187 | # Now fill in the overall length | |||
|
188 | obj_len = bser_buf.wpos - len(EMPTY_HEADER) | |||
|
189 | struct.pack_into('=i', bser_buf.buf, 3, obj_len) | |||
|
190 | return bser_buf.buf.raw[:bser_buf.wpos] | |||
|
191 | ||||
|
192 | ||||
|
193 | def _bunser_int(buf, pos): | |||
|
194 | try: | |||
|
195 | int_type = buf[pos] | |||
|
196 | except IndexError: | |||
|
197 | raise ValueError('Invalid bser int encoding, pos out of range') | |||
|
198 | if int_type == BSER_INT8: | |||
|
199 | needed = 2 | |||
|
200 | fmt = '=b' | |||
|
201 | elif int_type == BSER_INT16: | |||
|
202 | needed = 3 | |||
|
203 | fmt = '=h' | |||
|
204 | elif int_type == BSER_INT32: | |||
|
205 | needed = 5 | |||
|
206 | fmt = '=i' | |||
|
207 | elif int_type == BSER_INT64: | |||
|
208 | needed = 9 | |||
|
209 | fmt = '=q' | |||
|
210 | else: | |||
|
211 | raise ValueError('Invalid bser int encoding 0x%02x' % int(int_type)) | |||
|
212 | int_val = struct.unpack_from(fmt, buf, pos + 1)[0] | |||
|
213 | return (int_val, pos + needed) | |||
|
214 | ||||
|
215 | ||||
|
216 | def _bunser_string(buf, pos): | |||
|
217 | str_len, pos = _bunser_int(buf, pos + 1) | |||
|
218 | str_val = struct.unpack_from(str(str_len) + 's', buf, pos)[0] | |||
|
219 | return (str_val, pos + str_len) | |||
|
220 | ||||
|
221 | ||||
|
222 | def _bunser_array(buf, pos, mutable=True): | |||
|
223 | arr_len, pos = _bunser_int(buf, pos + 1) | |||
|
224 | arr = [] | |||
|
225 | for i in range(arr_len): | |||
|
226 | arr_item, pos = _bser_loads_recursive(buf, pos, mutable) | |||
|
227 | arr.append(arr_item) | |||
|
228 | ||||
|
229 | if not mutable: | |||
|
230 | arr = tuple(arr) | |||
|
231 | ||||
|
232 | return arr, pos | |||
|
233 | ||||
|
234 | ||||
|
235 | # This is a quack-alike with the bserObjectType in bser.c | |||
|
236 | # It provides by getattr accessors and getitem for both index | |||
|
237 | # and name. | |||
|
238 | class _BunserDict(object): | |||
|
239 | __slots__ = ('_keys', '_values') | |||
|
240 | ||||
|
241 | def __init__(self, keys, values): | |||
|
242 | self._keys = keys | |||
|
243 | self._values = values | |||
|
244 | ||||
|
245 | def __getattr__(self, name): | |||
|
246 | return self.__getitem__(name) | |||
|
247 | ||||
|
248 | def __getitem__(self, key): | |||
|
249 | if isinstance(key, (int, long)): | |||
|
250 | return self._values[key] | |||
|
251 | elif key.startswith('st_'): | |||
|
252 | # hack^Wfeature to allow mercurial to use "st_size" to | |||
|
253 | # reference "size" | |||
|
254 | key = key[3:] | |||
|
255 | try: | |||
|
256 | return self._values[self._keys.index(key)] | |||
|
257 | except ValueError as ex: | |||
|
258 | raise KeyError('_BunserDict has no key %s' % key) | |||
|
259 | ||||
|
260 | def __len__(self): | |||
|
261 | return len(self._keys) | |||
|
262 | ||||
|
263 | def _bunser_object(buf, pos, mutable=True): | |||
|
264 | obj_len, pos = _bunser_int(buf, pos + 1) | |||
|
265 | if mutable: | |||
|
266 | obj = {} | |||
|
267 | else: | |||
|
268 | keys = [] | |||
|
269 | vals = [] | |||
|
270 | ||||
|
271 | for i in range(obj_len): | |||
|
272 | key, pos = _bunser_string(buf, pos) | |||
|
273 | val, pos = _bser_loads_recursive(buf, pos, mutable) | |||
|
274 | if mutable: | |||
|
275 | obj[key] = val | |||
|
276 | else: | |||
|
277 | keys.append(key) | |||
|
278 | vals.append(val) | |||
|
279 | ||||
|
280 | if not mutable: | |||
|
281 | obj = _BunserDict(keys, vals) | |||
|
282 | ||||
|
283 | return obj, pos | |||
|
284 | ||||
|
285 | ||||
|
286 | def _bunser_template(buf, pos, mutable=True): | |||
|
287 | if buf[pos + 1] != BSER_ARRAY: | |||
|
288 | raise RuntimeError('Expect ARRAY to follow TEMPLATE') | |||
|
289 | keys, pos = _bunser_array(buf, pos + 1) | |||
|
290 | nitems, pos = _bunser_int(buf, pos) | |||
|
291 | arr = [] | |||
|
292 | for i in range(nitems): | |||
|
293 | if mutable: | |||
|
294 | obj = {} | |||
|
295 | else: | |||
|
296 | vals = [] | |||
|
297 | ||||
|
298 | for keyidx in range(len(keys)): | |||
|
299 | if buf[pos] == BSER_SKIP: | |||
|
300 | pos += 1 | |||
|
301 | ele = None | |||
|
302 | else: | |||
|
303 | ele, pos = _bser_loads_recursive(buf, pos, mutable) | |||
|
304 | ||||
|
305 | if mutable: | |||
|
306 | key = keys[keyidx] | |||
|
307 | obj[key] = ele | |||
|
308 | else: | |||
|
309 | vals.append(ele) | |||
|
310 | ||||
|
311 | if not mutable: | |||
|
312 | obj = _BunserDict(keys, vals) | |||
|
313 | ||||
|
314 | arr.append(obj) | |||
|
315 | return arr, pos | |||
|
316 | ||||
|
317 | ||||
|
318 | def _bser_loads_recursive(buf, pos, mutable=True): | |||
|
319 | val_type = buf[pos] | |||
|
320 | if (val_type == BSER_INT8 or val_type == BSER_INT16 or | |||
|
321 | val_type == BSER_INT32 or val_type == BSER_INT64): | |||
|
322 | return _bunser_int(buf, pos) | |||
|
323 | elif val_type == BSER_REAL: | |||
|
324 | val = struct.unpack_from('=d', buf, pos + 1)[0] | |||
|
325 | return (val, pos + 9) | |||
|
326 | elif val_type == BSER_TRUE: | |||
|
327 | return (True, pos + 1) | |||
|
328 | elif val_type == BSER_FALSE: | |||
|
329 | return (False, pos + 1) | |||
|
330 | elif val_type == BSER_NULL: | |||
|
331 | return (None, pos + 1) | |||
|
332 | elif val_type == BSER_STRING: | |||
|
333 | return _bunser_string(buf, pos) | |||
|
334 | elif val_type == BSER_ARRAY: | |||
|
335 | return _bunser_array(buf, pos, mutable) | |||
|
336 | elif val_type == BSER_OBJECT: | |||
|
337 | return _bunser_object(buf, pos, mutable) | |||
|
338 | elif val_type == BSER_TEMPLATE: | |||
|
339 | return _bunser_template(buf, pos, mutable) | |||
|
340 | else: | |||
|
341 | raise RuntimeError('unhandled bser opcode 0x%02x' % (val_type,)) | |||
|
342 | ||||
|
343 | ||||
|
344 | def pdu_len(buf): | |||
|
345 | if buf[0:2] != EMPTY_HEADER[0:2]: | |||
|
346 | raise RuntimeError('Invalid BSER header') | |||
|
347 | expected_len, pos = _bunser_int(buf, 2) | |||
|
348 | return expected_len + pos | |||
|
349 | ||||
|
350 | ||||
|
351 | def loads(buf, mutable=True): | |||
|
352 | if buf[0:2] != EMPTY_HEADER[0:2]: | |||
|
353 | raise RuntimeError('Invalid BSER header') | |||
|
354 | expected_len, pos = _bunser_int(buf, 2) | |||
|
355 | if len(buf) != expected_len + pos: | |||
|
356 | raise RuntimeError('bser data len != header len') | |||
|
357 | return _bser_loads_recursive(buf, pos, mutable)[0] | |||
|
358 | ||||
|
359 | # no-check-code -- this is a 3rd party library |
@@ -532,6 +532,8 b' extmodules = [' | |||||
532 | Extension('mercurial.osutil', ['mercurial/osutil.c'], |
|
532 | Extension('mercurial.osutil', ['mercurial/osutil.c'], | |
533 | extra_link_args=osutil_ldflags, |
|
533 | extra_link_args=osutil_ldflags, | |
534 | depends=common_depends), |
|
534 | depends=common_depends), | |
|
535 | Extension('hgext.fsmonitor.pywatchman.bser', | |||
|
536 | ['hgext/fsmonitor/pywatchman/bser.c']), | |||
535 | ] |
|
537 | ] | |
536 |
|
538 | |||
537 | try: |
|
539 | try: |
@@ -8,6 +8,10 b' New errors are not allowed. Warnings are' | |||||
8 |
|
8 | |||
9 | $ hg locate | sed 's-\\-/-g' | |
|
9 | $ hg locate | sed 's-\\-/-g' | | |
10 | > xargs "$check_code" --warnings --per-file=0 || false |
|
10 | > xargs "$check_code" --warnings --per-file=0 || false | |
|
11 | Skipping hgext/fsmonitor/pywatchman/__init__.py it has no-che?k-code (glob) | |||
|
12 | Skipping hgext/fsmonitor/pywatchman/bser.c it has no-che?k-code (glob) | |||
|
13 | Skipping hgext/fsmonitor/pywatchman/capabilities.py it has no-che?k-code (glob) | |||
|
14 | Skipping hgext/fsmonitor/pywatchman/pybser.py it has no-che?k-code (glob) | |||
11 | Skipping i18n/polib.py it has no-che?k-code (glob) |
|
15 | Skipping i18n/polib.py it has no-che?k-code (glob) | |
12 | Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob) |
|
16 | Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob) | |
13 | Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob) |
|
17 | Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob) |
@@ -25,6 +25,10 b'' | |||||
25 | hgext/extdiff.py not using absolute_import |
|
25 | hgext/extdiff.py not using absolute_import | |
26 | hgext/factotum.py not using absolute_import |
|
26 | hgext/factotum.py not using absolute_import | |
27 | hgext/fetch.py not using absolute_import |
|
27 | hgext/fetch.py not using absolute_import | |
|
28 | hgext/fsmonitor/pywatchman/__init__.py not using absolute_import | |||
|
29 | hgext/fsmonitor/pywatchman/__init__.py requires print_function | |||
|
30 | hgext/fsmonitor/pywatchman/capabilities.py not using absolute_import | |||
|
31 | hgext/fsmonitor/pywatchman/pybser.py not using absolute_import | |||
28 | hgext/gpg.py not using absolute_import |
|
32 | hgext/gpg.py not using absolute_import | |
29 | hgext/graphlog.py not using absolute_import |
|
33 | hgext/graphlog.py not using absolute_import | |
30 | hgext/hgcia.py not using absolute_import |
|
34 | hgext/hgcia.py not using absolute_import |
General Comments 0
You need to be logged in to leave comments.
Login now