rpc.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. import xmlrpclib
  2. import os
  3. from supervisord import ProcessStates
  4. from supervisord import SupervisorStates
  5. from supervisord import getSupervisorStateDescription
  6. import doctags
  7. import signal
  8. import time
  9. from medusa.xmlrpc_handler import xmlrpc_handler
  10. from medusa.http_server import get_header
  11. from medusa import producers
  12. import sys
  13. import types
  14. import re
  15. import traceback
  16. import StringIO
  17. import tempfile
  18. import errno
  19. from http import NOT_DONE_YET
  20. RPC_VERSION = 1.0
  21. class Faults:
  22. UNKNOWN_METHOD = 1
  23. INCORRECT_PARAMETERS = 2
  24. BAD_ARGUMENTS = 3
  25. SIGNATURE_UNSUPPORTED = 4
  26. SHUTDOWN_STATE = 6
  27. BAD_NAME = 10
  28. NO_FILE = 20
  29. FAILED = 30
  30. ABNORMAL_TERMINATION = 40
  31. SPAWN_ERROR = 50
  32. ALREADY_STARTED = 60
  33. NOT_RUNNING = 70
  34. def getFaultDescription(code):
  35. for faultname in Faults.__dict__:
  36. if getattr(Faults, faultname) == code:
  37. return faultname
  38. return 'UNKNOWN'
  39. class RPCError(Exception):
  40. def __init__(self, code):
  41. self.code = code
  42. self.text = getFaultDescription(code)
  43. class DeferredXMLRPCResponse:
  44. """ A medusa producer that implements a deferred callback; requires
  45. a subclass of asynchat.async_chat that handles NOT_DONE_YET sentinel """
  46. CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
  47. def __init__(self, request, callback):
  48. self.callback = callback
  49. self.request = request
  50. self.finished = False
  51. self.delay = float(callback.delay)
  52. def more(self):
  53. if self.finished:
  54. return ''
  55. try:
  56. try:
  57. value = self.callback()
  58. if value is NOT_DONE_YET:
  59. return NOT_DONE_YET
  60. except RPCError, err:
  61. value = xmlrpclib.Fault(err.code, err.text)
  62. body = xmlrpc_marshal(value)
  63. self.finished = True
  64. return self.getresponse(body)
  65. except:
  66. # report unexpected exception back to server
  67. traceback.print_exc()
  68. self.finished = True
  69. self.request.error(500)
  70. def getresponse(self, body):
  71. self.request['Content-Type'] = 'text/xml'
  72. self.request['Content-Length'] = len(body)
  73. self.request.push(body)
  74. connection = get_header(self.CONNECTION, self.request.header)
  75. close_it = 0
  76. wrap_in_chunking = 0
  77. if self.request.version == '1.0':
  78. if connection == 'keep-alive':
  79. if not self.request.has_key ('Content-Length'):
  80. close_it = 1
  81. else:
  82. self.request['Connection'] = 'Keep-Alive'
  83. else:
  84. close_it = 1
  85. elif self.request.version == '1.1':
  86. if connection == 'close':
  87. close_it = 1
  88. elif not self.request.has_key ('Content-Length'):
  89. if self.request.has_key ('Transfer-Encoding'):
  90. if not self.request['Transfer-Encoding'] == 'chunked':
  91. close_it = 1
  92. elif self.request.use_chunked:
  93. self.request['Transfer-Encoding'] = 'chunked'
  94. wrap_in_chunking = 1
  95. else:
  96. close_it = 1
  97. elif self.request.version is None:
  98. close_it = 1
  99. outgoing_header = producers.simple_producer (
  100. self.request.build_reply_header())
  101. if close_it:
  102. self.request['Connection'] = 'close'
  103. if wrap_in_chunking:
  104. outgoing_producer = producers.chunked_producer (
  105. producers.composite_producer (self.request.outgoing)
  106. )
  107. # prepend the header
  108. outgoing_producer = producers.composite_producer(
  109. [outgoing_header, outgoing_producer]
  110. )
  111. else:
  112. # prepend the header
  113. self.request.outgoing.insert(0, outgoing_header)
  114. outgoing_producer = producers.composite_producer (
  115. self.request.outgoing)
  116. # apply a few final transformations to the output
  117. self.request.channel.push_with_producer (
  118. # globbing gives us large packets
  119. producers.globbing_producer (
  120. # hooking lets us log the number of bytes sent
  121. producers.hooked_producer (
  122. outgoing_producer,
  123. self.request.log
  124. )
  125. )
  126. )
  127. self.request.channel.current_request = None
  128. if close_it:
  129. self.request.channel.close_when_done()
  130. def xmlrpc_marshal(value):
  131. ismethodresponse = not isinstance(value, xmlrpclib.Fault)
  132. if ismethodresponse:
  133. if not isinstance(value, tuple):
  134. value = (value,)
  135. body = xmlrpclib.dumps(value, methodresponse=ismethodresponse)
  136. else:
  137. body = xmlrpclib.dumps(value)
  138. return body
  139. class SupervisorNamespaceRPCInterface:
  140. def __init__(self, supervisord):
  141. self.supervisord = supervisord
  142. def _update(self, text):
  143. self.update_text = text # for unit tests, mainly
  144. state = self.supervisord.get_state()
  145. if state == SupervisorStates.SHUTDOWN:
  146. raise RPCError(Faults.SHUTDOWN_STATE)
  147. # RPC API methods
  148. def getVersion(self):
  149. """ Return the version of the RPC API used by supervisord
  150. @return int version version id
  151. """
  152. self._update('getVersion')
  153. return RPC_VERSION
  154. def getIdentification(self):
  155. """ Return identifiying string of supervisord
  156. @return string identifier identifying string
  157. """
  158. self._update('getIdentification')
  159. return self.supervisord.options.identifier
  160. def getState(self):
  161. """ Return current state of supervisord as a struct
  162. @return struct A struct with keys string statecode, int statename
  163. """
  164. self._update('getState')
  165. state = self.supervisord.get_state()
  166. statename = getSupervisorStateDescription(state)
  167. data = {
  168. 'statecode':state,
  169. 'statename':statename,
  170. }
  171. return data
  172. def readLog(self, offset, length):
  173. """ Read length bytes from the main log starting at offset.
  174. @param int offset offset to start reading from.
  175. @param int length number of bytes to read from the log.
  176. @return struct data a struct with keys 'log' (value of 'log' is string).
  177. """
  178. self._update('readLog')
  179. logfile = self.supervisord.options.logfile
  180. if logfile is None or not os.path.exists(logfile):
  181. raise RPCError(Faults.NO_FILE)
  182. try:
  183. return _readFile(logfile, offset, length)
  184. except ValueError, inst:
  185. why = inst.args[0]
  186. raise RPCError(getattr(Faults, why))
  187. def clearLog(self):
  188. """ Clear the main log.
  189. @return boolean result always returns True unless error
  190. """
  191. self._update('clearLog')
  192. logfile = self.supervisord.options.logfile
  193. if logfile is None or not os.path.exists(logfile):
  194. raise RPCError(Faults.NO_FILE)
  195. try:
  196. os.remove(logfile) # there is a race condition here, but ignore it.
  197. except (os.error, IOError):
  198. raise RPCError(Faults.FAILED)
  199. for handler in self.supervisord.options.logger.handlers:
  200. if hasattr(handler, 'reopen'):
  201. self.supervisord.options.logger.info('reopening log file')
  202. handler.reopen()
  203. return True
  204. def shutdown(self):
  205. """ Shut down the supervisor process
  206. @return boolean result always returns True unless error
  207. """
  208. self._update('shutdown')
  209. self.supervisord.mood = -1
  210. return True
  211. def restart(self):
  212. """ Restart the supervisor process
  213. @return boolean result always return True unless error
  214. """
  215. self._update('restart')
  216. self.supervisord.mood = 0
  217. return True
  218. def startProcess(self, name, timeout=500):
  219. """ Start a process
  220. @param string name Process name
  221. @param int timeout Number of milliseconds to wait for process start
  222. @return boolean result Always true unless error
  223. """
  224. self._update('startProcess')
  225. processes = self.supervisord.processes
  226. process = processes.get(name)
  227. if process is None:
  228. raise RPCError(Faults.BAD_NAME)
  229. if process.pid:
  230. raise RPCError(Faults.ALREADY_STARTED)
  231. process.spawn()
  232. if process.spawnerr:
  233. raise RPCError(Faults.SPAWN_ERROR)
  234. if not timeout:
  235. return True
  236. milliseconds = timeout / 1000.0
  237. start = time.time()
  238. def check_still_running(done=False): # done arg is only for unit testing
  239. t = time.time()
  240. runtime = (t - start)
  241. if not done and runtime < milliseconds:
  242. return NOT_DONE_YET
  243. pid = processes[name].pid
  244. if pid:
  245. return True
  246. raise RPCError(Faults.ABNORMAL_TERMINATION)
  247. check_still_running.delay = milliseconds
  248. check_still_running.rpcinterface = self
  249. return check_still_running # deferred
  250. def startAllProcesses(self, timeout=500):
  251. """ Start all processes listed in the configuration file
  252. @param int timeout Number of milliseconds to wait for each process start
  253. @return boolean result Always true unless error
  254. """
  255. self._update('startAllProcesses')
  256. processes = self.supervisord.processes
  257. callbacks = []
  258. processnames = processes.keys()
  259. processnames.sort()
  260. for processname in processnames:
  261. process = processes[processname]
  262. if process.get_state() != ProcessStates.RUNNING:
  263. # only start nonrunning processes
  264. callbacks.append(self.startProcess(processname, timeout))
  265. def startall(done=False): # done arg is for unit testing
  266. if not callbacks:
  267. return True
  268. callback = callbacks.pop(0)
  269. value = callback(done)
  270. if value is NOT_DONE_YET:
  271. # push it back into the queue; it will finish eventually
  272. callbacks.append(callback)
  273. if callbacks:
  274. return NOT_DONE_YET
  275. return True
  276. # XXX the above implementation has a weakness inasmuch as the
  277. # first call into each individual process callback will always
  278. # return NOT_DONE_YET, so they need to be called twice. The
  279. # symptom of this is that calling this method causes the
  280. # client to block for much longer than it actually requires to
  281. # start all of the nonrunning processes. See stopAllProcesses
  282. startall.delay = 0.05
  283. startall.rpcinterface = self
  284. return startall # deferred
  285. def stopProcess(self, name):
  286. """ Stop a process named by name
  287. @param string name The name of the process to stop
  288. @return boolean result Always return True unless error
  289. """
  290. self._update('stopProcess')
  291. process = self.supervisord.processes.get(name)
  292. if process is None:
  293. raise RPCError(Faults.BAD_NAME)
  294. if process.get_state() != ProcessStates.RUNNING:
  295. raise RPCEror(Faults.NOT_RUNNING)
  296. def killit():
  297. if process.killing:
  298. return NOT_DONE_YET
  299. elif process.pid:
  300. msg = process.stop()
  301. if msg is not None:
  302. raise RPCError(Faults.FAILED)
  303. return NOT_DONE_YET
  304. else:
  305. return True
  306. killit.delay = 0.2
  307. killit.rpcinterface = self
  308. return killit # deferred
  309. def stopAllProcesses(self):
  310. """ Stop all processes in the process list
  311. @return boolean result Always return true unless error.
  312. """
  313. self._update('stopAllProcesses')
  314. processes = self.supervisord.processes
  315. callbacks = []
  316. processnames = processes.keys()
  317. processnames.sort()
  318. for processname in processnames:
  319. process = processes[processname]
  320. if process.pid:
  321. # only stop running processes
  322. callbacks.append(self.stopProcess(processname))
  323. def killall():
  324. if not callbacks:
  325. return True
  326. callback = callbacks.pop(0)
  327. value = callback()
  328. if value is NOT_DONE_YET:
  329. # push it back into the queue; it will finish eventually
  330. callbacks.append(callback)
  331. if callbacks:
  332. return NOT_DONE_YET
  333. return True
  334. # XXX the above implementation has a weakness inasmuch as the
  335. # first call into each individual process callback will always
  336. # return NOT_DONE_YET, so they need to be called twice. The
  337. # symptom of this is that calling this method causes the
  338. # client to block for much longer than it actually requires to
  339. # kill all of the running processes. After the first call to
  340. # the killit callback, the process is actually dead, but the
  341. # above killall method processes the callbacks one at a time
  342. # during the select loop, which, because there is no output
  343. # from child processes after stopAllProcesses is called, is
  344. # not busy, so hits the timeout for each callback. I
  345. # attempted to make this better, but the only way to make it
  346. # better assumes totally synchronous reaping of child
  347. # processes, which requires infrastructure changes to
  348. # supervisord that are scary at the moment as it could take a
  349. # while to pin down all of the platform differences and might
  350. # require a C extension to the Python signal module to allow
  351. # the setting of ignore flags to signals.
  352. killall.delay = 0.05
  353. killall.rpcinterface = self
  354. return killall # deferred
  355. def getProcessInfo(self, name):
  356. """ Get info about a process named name
  357. @param string name The name of the process
  358. @return struct result A structure containing data about the process
  359. """
  360. self._update('getProcessInfo')
  361. process = self.supervisord.processes.get(name)
  362. if process is None:
  363. raise RPCError(Faults.BAD_NAME)
  364. start = int(process.laststart)
  365. stop = int(process.laststop)
  366. now = int(time.time())
  367. state = process.get_state()
  368. spawnerr = process.spawnerr or ''
  369. exitstatus = process.exitstatus or 0
  370. reportstatusmsg = process.reportstatusmsg or ''
  371. return {
  372. 'name':name,
  373. 'start':start,
  374. 'stop':stop,
  375. 'now':now,
  376. 'state':state,
  377. 'spawnerr':spawnerr,
  378. 'exitstatus':exitstatus,
  379. 'reportstatusmsg':reportstatusmsg,
  380. 'logfile':process.config.logfile,
  381. 'pid':process.pid
  382. }
  383. def getAllProcessInfo(self):
  384. """ Get info about all processes
  385. @return array result An array of process status results
  386. """
  387. self._update('getAllProcessInfo')
  388. processnames = self.supervisord.processes.keys()
  389. processnames.sort()
  390. output = []
  391. for processname in processnames:
  392. output.append(self.getProcessInfo(processname))
  393. return output
  394. def readProcessLog(self, processName, offset, length):
  395. """ Read length bytes from processName's log starting at offset
  396. @param string processName The name of the process
  397. @param int offset offset to start reading from.
  398. @param int length number of bytes to read from the log.
  399. @return string result Bytes of log
  400. """
  401. self._update('readProcessLog')
  402. process = self.supervisord.processes.get(processName)
  403. if process is None:
  404. raise RPCError(Faults.BAD_NAME)
  405. logfile = process.config.logfile
  406. if logfile is None or not os.path.exists(logfile):
  407. raise RPCError(Faults.NO_FILE)
  408. try:
  409. return _readFile(logfile, offset, length)
  410. except ValueError, inst:
  411. why = inst.args[0]
  412. raise RPCError(getattr(Faults, why))
  413. def clearProcessLog(self, processName):
  414. """ Clear the log for processName and reopen it
  415. @param string processName The name of the process
  416. @return boolean result Always True unless error
  417. """
  418. self._update('clearProcessLog')
  419. process = self.supervisord.processes.get(processName)
  420. if process is None:
  421. raise RPCError(Faults.BAD_NAME)
  422. try:
  423. # implies a reopen
  424. process.removelogs()
  425. except (IOError, os.error):
  426. raise RPCError(Faults.FAILED)
  427. return True
  428. def _rotateMainLog(self):
  429. """ Rotate the main supervisord log (for debugging/testing) """
  430. self._update('_rotateMainLog')
  431. for handler in self.supervisord.options.logger.handlers:
  432. if hasattr(handler, 'doRollover'):
  433. handler.doRollover()
  434. return True
  435. class SystemNamespaceRPCInterface:
  436. def __init__(self, namespaces):
  437. self.namespaces = {}
  438. for name, inst in namespaces:
  439. self.namespaces[name] = inst
  440. self.namespaces['system'] = self
  441. def _listMethods(self):
  442. methods = {}
  443. for ns_name in self.namespaces:
  444. namespace = self.namespaces[ns_name]
  445. for method_name in namespace.__class__.__dict__:
  446. # introspect; any methods that don't start with underscore
  447. # are published
  448. func = getattr(namespace, method_name)
  449. meth = getattr(func, 'im_func', None)
  450. if meth is not None:
  451. if not method_name.startswith('_'):
  452. sig = '%s.%s' % (ns_name, method_name)
  453. methods[sig] = str(func.__doc__)
  454. return methods
  455. def listMethods(self):
  456. """ Return an array listing the available method names
  457. @return array result An array of method names available (strings).
  458. """
  459. methods = self._listMethods()
  460. keys = methods.keys()
  461. keys.sort()
  462. return keys
  463. def methodHelp(self, name):
  464. """ Return a string showing the method's documentation
  465. @param string name The name of the method.
  466. @return string result The documentation for the method name.
  467. """
  468. methods = self._listMethods()
  469. for methodname in methods.keys():
  470. if methodname == name:
  471. return methods[methodname]
  472. raise RPCError(Faults.SIGNATURE_UNSUPPORTED)
  473. def methodSignature(self, name):
  474. """ Return an array describing the method signature in the
  475. form [rtype, ptype, ptype...] where rtype is the return data type
  476. of the method, and ptypes are the parameter data types that the
  477. method accepts in method argument order.
  478. @param string name The name of the method.
  479. @return array result The result.
  480. """
  481. methods = self._listMethods()
  482. L = []
  483. for method in methods:
  484. if method == name:
  485. rtype = None
  486. ptypes = []
  487. parsed = doctags.gettags(methods[method])
  488. for thing in parsed:
  489. if thing[1] == 'return': # tag name
  490. rtype = thing[2] # datatype
  491. elif thing[1] == 'param': # tag name
  492. ptypes.append(thing[2]) # datatype
  493. if rtype is None:
  494. raise RPCError(Faults.SIGNATURE_UNSUPPORTED)
  495. return [rtype] + ptypes
  496. raise RPCError(Faults.SIGNATURE_UNSUPPORTED)
  497. def multicall(self, calls):
  498. """Process an array of calls, and return an array of
  499. results. Calls should be structs of the form {'methodName':
  500. string, 'params': array}. Each result will either be a
  501. single-item array containg the result value, or a struct of
  502. the form {'faultCode': int, 'faultString': string}. This is
  503. useful when you need to make lots of small calls without lots
  504. of round trips.
  505. @param array calls An array of call requests
  506. @return array result An array of results
  507. """
  508. producers = []
  509. for call in calls:
  510. try:
  511. name = call['methodName']
  512. params = call.get('params', [])
  513. if name == 'system.multicall':
  514. # Recursive system.multicall forbidden
  515. error = 'INCORRECT_PARAMETERS'
  516. raise xmlrpclib.Fault(Faults.INCORRECT_PARAMETERS,
  517. error)
  518. root = AttrDict(self.namespaces)
  519. value = traverse(root, name, params)
  520. except RPCError, inst:
  521. value = {'faultCode': inst.code,
  522. 'faultString': inst.text}
  523. except:
  524. errmsg = "%s:%s" % (sys.exc_type, sys.exc_value)
  525. value = {'faultCode': 1, 'faultString': errmsg}
  526. producers.append(value)
  527. results = []
  528. def multiproduce():
  529. """ Run through all the producers in order """
  530. if not producers:
  531. return []
  532. callback = producers.pop(0)
  533. if isinstance(callback, types.FunctionType):
  534. try:
  535. value = callback()
  536. except RPCError, inst:
  537. value = {'faultCode':inst.code, 'faultString':inst.text}
  538. if value is NOT_DONE_YET:
  539. # push it back in the front of the queue because we
  540. # need to finish the calls in requested order
  541. producers.insert(0, callback)
  542. return NOT_DONE_YET
  543. else:
  544. value = callback
  545. results.append(value)
  546. if producers:
  547. # only finish when all producers are finished
  548. return NOT_DONE_YET
  549. return results
  550. multiproduce.delay = .05
  551. return multiproduce
  552. class AttrDict(dict):
  553. # hack to make a dict's getattr equivalent to its getitem
  554. def __getattr__(self, name):
  555. return self[name]
  556. class RPCInterface:
  557. def __init__(self, supervisord):
  558. self.supervisord = supervisord
  559. self.supervisor = SupervisorNamespaceRPCInterface(supervisord)
  560. self.system = SystemNamespaceRPCInterface(
  561. [('supervisor', self.supervisor)]
  562. )
  563. class supervisor_xmlrpc_handler(xmlrpc_handler):
  564. def __init__(self, supervisord):
  565. self.rpcinterface = RPCInterface(supervisord)
  566. self.supervisord = supervisord
  567. def continue_request (self, data, request):
  568. logger = self.supervisord.options.logger
  569. try:
  570. params, method = xmlrpclib.loads(data)
  571. try:
  572. # 5 is 'trace' level
  573. logger.log(5, 'XML-RPC method called: %s()' % method)
  574. value = self.call(method, params)
  575. logger.log(5, 'XML-RPC method %s() returned successfully' %
  576. method)
  577. except RPCError, err:
  578. # turn RPCError reported by method into a Fault instance
  579. value = xmlrpclib.Fault(err.code, err.text)
  580. logger.warn('XML-RPC method %s() returned fault: [%d] %s' % (
  581. method,
  582. err.code, err.text))
  583. if isinstance(value, types.FunctionType):
  584. # returning a function from an RPC method implies that
  585. # this needs to be a deferred response (it needs to block).
  586. pushproducer = request.channel.push_with_producer
  587. pushproducer(DeferredXMLRPCResponse(request, value))
  588. else:
  589. # if we get anything but a function, it implies that this
  590. # response doesn't need to be deferred, we can service it
  591. # right away.
  592. body = xmlrpc_marshal(value)
  593. request['Content-Type'] = 'text/xml'
  594. request['Content-Length'] = len(body)
  595. request.push(body)
  596. request.done()
  597. except:
  598. io = StringIO.StringIO()
  599. traceback.print_exc(file=io)
  600. val = io.getvalue()
  601. logger.critical(val)
  602. # internal error, report as HTTP server error
  603. request.error(500)
  604. def call(self, method, params):
  605. return traverse(self.rpcinterface, method, params)
  606. def traverse(ob, method, params):
  607. path = method.split('.')
  608. for name in path:
  609. if name.startswith('_'):
  610. # security (don't allow things that start with an underscore to
  611. # be called remotely)
  612. raise RPCError(Faults.UNKNOWN_METHOD)
  613. ob = getattr(ob, name, None)
  614. if ob is None:
  615. raise RPCError(Faults.UNKNOWN_METHOD)
  616. try:
  617. return ob(*params)
  618. except TypeError:
  619. raise RPCError(Faults.INCORRECT_PARAMETERS)
  620. def _readFile(filename, offset, length):
  621. """ Read length bytes from the file named by filename starting at
  622. offset """
  623. absoffset = abs(offset)
  624. abslength = abs(length)
  625. try:
  626. f = open(filename, 'rb')
  627. if absoffset != offset:
  628. # negative offset returns offset bytes from tail of the file
  629. if length:
  630. raise ValueError('BAD_ARGUMENTS')
  631. f.seek(0, 2)
  632. sz = f.tell()
  633. pos = int(sz - absoffset)
  634. f.seek(pos)
  635. data = f.read(absoffset)
  636. else:
  637. if abslength != length:
  638. raise ValueError('BAD_ARGUMENTS')
  639. if length == 0:
  640. f.seek(offset)
  641. data = f.read()
  642. else:
  643. sz = f.seek(offset)
  644. data = f.read(length)
  645. except (os.error, IOError):
  646. raise ValueError('FAILED')
  647. return data