1#!/usr/bin/env python 2""" 3Binary memcached test client. 4 5Copyright (c) 2007 Dustin Sallings <dustin@spy.net> 6""" 7 8import hmac 9import socket 10import select 11import random 12import struct 13import exceptions 14import zlib 15 16from memcacheConstants import REQ_MAGIC_BYTE, RES_MAGIC_BYTE 17from memcacheConstants import REQ_PKT_FMT, RES_PKT_FMT, MIN_RECV_PACKET 18from memcacheConstants import SET_PKT_FMT, DEL_PKT_FMT, INCRDECR_RES_FMT 19from memcacheConstants import TOUCH_PKT_FMT, GAT_PKT_FMT, GETL_PKT_FMT 20import memcacheConstants 21 22class MemcachedError(exceptions.Exception): 23 """Error raised when a command fails.""" 24 25 def __init__(self, status, msg): 26 error_msg = error_to_str(status) 27 supermsg = 'Memcached error #' + `status` + ' ' + `error_msg` 28 if msg: supermsg += ": " + msg 29 exceptions.Exception.__init__(self, supermsg) 30 31 self.status = status 32 self.msg = msg 33 34 def __repr__(self): 35 return "<MemcachedError #%d ``%s''>" % (self.status, self.msg) 36 37class MemcachedClient(object): 38 """Simple memcached client.""" 39 40 vbucketId = 0 41 42 def __init__(self, host='127.0.0.1', port=11211, timeout=30): 43 self.host = host 44 self.port = port 45 self.timeout = timeout 46 self._createConn() 47 self.r = random.Random() 48 self.vbucket_count = 1024 49 50 def _createConn(self): 51 self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 52 self.s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 53 return self.s.connect_ex((self.host, self.port)) 54 55 def reconnect(self): 56 self.s.close() 57 return self._createConn() 58 59 def close(self): 60 self.s.close() 61 62 def __del__(self): 63 self.close() 64 65 def _sendCmd(self, cmd, key, val, opaque, extraHeader='', cas=0): 66 self._sendMsg(cmd, key, val, opaque, extraHeader=extraHeader, cas=cas, 67 vbucketId=self.vbucketId) 68 69 def _sendMsg(self, cmd, key, val, opaque, extraHeader='', cas=0, 70 dtype=0, vbucketId=0, 71 fmt=REQ_PKT_FMT, magic=REQ_MAGIC_BYTE): 72 msg = struct.pack(fmt, magic, 73 cmd, len(key), len(extraHeader), dtype, vbucketId, 74 len(key) + len(extraHeader) + len(val), opaque, cas) 75 _, w, _ = select.select([], [self.s], [], self.timeout) 76 if w: 77 self.s.send(msg + extraHeader + key + val) 78 else: 79 raise exceptions.EOFError("Timeout waiting for socket send. from {0}".format(self.host)) 80 81 def _recvMsg(self): 82 response = "" 83 while len(response) < MIN_RECV_PACKET: 84 r, _, _ = select.select([self.s], [], [], self.timeout) 85 if r: 86 data = self.s.recv(MIN_RECV_PACKET - len(response)) 87 if data == '': 88 raise exceptions.EOFError("Got empty data (remote died?). from {0}".format(self.host)) 89 response += data 90 else: 91 raise exceptions.EOFError("Timeout waiting for socket recv. from {0}".format(self.host)) 92 assert len(response) == MIN_RECV_PACKET 93 magic, cmd, keylen, extralen, dtype, errcode, remaining, opaque, cas = \ 94 struct.unpack(RES_PKT_FMT, response) 95 96 rv = "" 97 while remaining > 0: 98 r, _, _ = select.select([self.s], [], [], self.timeout) 99 if r: 100 data = self.s.recv(remaining) 101 if data == '': 102 raise exceptions.EOFError("Got empty data (remote died?). from {0}".format(self.host)) 103 rv += data 104 remaining -= len(data) 105 else: 106 raise exceptions.EOFError("Timeout waiting for socket recv. from {0}".format(self.host)) 107 108 assert (magic in (RES_MAGIC_BYTE, REQ_MAGIC_BYTE)), "Got magic: %d" % magic 109 return cmd, errcode, opaque, cas, keylen, extralen, rv 110 111 def _handleKeyedResponse(self, myopaque): 112 cmd, errcode, opaque, cas, keylen, extralen, rv = self._recvMsg() 113 assert myopaque is None or opaque == myopaque, \ 114 "expected opaque %x, got %x" % (myopaque, opaque) 115 if errcode: 116 rv += " for vbucket :{0} to mc {1}:{2}".format(self.vbucketId, self.host, self.port) 117 raise MemcachedError(errcode, rv) 118 return cmd, opaque, cas, keylen, extralen, rv 119 120 def _handleSingleResponse(self, myopaque): 121 cmd, opaque, cas, keylen, extralen, data = self._handleKeyedResponse(myopaque) 122 return opaque, cas, data 123 124 def _doCmd(self, cmd, key, val, extraHeader='', cas=0): 125 """Send a command and await its response.""" 126 opaque = self.r.randint(0, 2 ** 32) 127 self._sendCmd(cmd, key, val, opaque, extraHeader, cas) 128 return self._handleSingleResponse(opaque) 129 130 def _mutate(self, cmd, key, exp, flags, cas, val): 131 return self._doCmd(cmd, key, val, struct.pack(SET_PKT_FMT, flags, exp), 132 cas) 133 134 def _cat(self, cmd, key, cas, val): 135 return self._doCmd(cmd, key, val, '', cas) 136 137 def append(self, key, value, cas=0, vbucket= -1): 138 self._set_vbucket(key, vbucket) 139 return self._cat(memcacheConstants.CMD_APPEND, key, cas, value) 140 141 def prepend(self, key, value, cas=0, vbucket= -1): 142 self._set_vbucket(key, vbucket) 143 return self._cat(memcacheConstants.CMD_PREPEND, key, cas, value) 144 145 def __incrdecr(self, cmd, key, amt, init, exp): 146 something, cas, val = self._doCmd(cmd, key, '', 147 struct.pack(memcacheConstants.INCRDECR_PKT_FMT, amt, init, exp)) 148 return struct.unpack(INCRDECR_RES_FMT, val)[0], cas 149 150 def incr(self, key, amt=1, init=0, exp=0, vbucket= -1): 151 """Increment or create the named counter.""" 152 self._set_vbucket(key, vbucket) 153 return self.__incrdecr(memcacheConstants.CMD_INCR, key, amt, init, exp) 154 155 def decr(self, key, amt=1, init=0, exp=0, vbucket= -1): 156 """Decrement or create the named counter.""" 157 self._set_vbucket(key, vbucket) 158 return self.__incrdecr(memcacheConstants.CMD_DECR, key, amt, init, exp) 159 160 def set(self, key, exp, flags, val, vbucket= -1): 161 """Set a value in the memcached server.""" 162 self._set_vbucket(key, vbucket) 163 return self._mutate(memcacheConstants.CMD_SET, key, exp, flags, 0, val) 164 165 def send_set(self, key, exp, flags, val, vbucket= -1): 166 """Set a value in the memcached server without handling the response""" 167 self._set_vbucket(key, vbucket) 168 opaque = self.r.randint(0, 2 ** 32) 169 self._sendCmd(memcacheConstants.CMD_SET, key, val, opaque, struct.pack(SET_PKT_FMT, flags, exp), 0) 170 171 def add(self, key, exp, flags, val, vbucket= -1): 172 """Add a value in the memcached server iff it doesn't already exist.""" 173 self._set_vbucket(key, vbucket) 174 return self._mutate(memcacheConstants.CMD_ADD, key, exp, flags, 0, val) 175 176 def replace(self, key, exp, flags, val, vbucket= -1): 177 """Replace a value in the memcached server iff it already exists.""" 178 self._set_vbucket(key, vbucket) 179 return self._mutate(memcacheConstants.CMD_REPLACE, key, exp, flags, 0, 180 val) 181 def observe(self, key, vbucket= -1): 182 """Observe a key for persistence and replication.""" 183 self._set_vbucket(key, vbucket) 184 value = struct.pack('>HH', self.vbucketId, len(key)) + key 185 opaque, cas, data = self._doCmd(memcacheConstants.CMD_OBSERVE, '', value) 186 rep_time = (cas & 0xFFFFFFFF) 187 persist_time = (cas >> 32) & 0xFFFFFFFF 188 persisted = struct.unpack('>B', data[4 + len(key)])[0] 189 return opaque, rep_time, persist_time, persisted, cas 190 191 def __parseGet(self, data, klen=0): 192 flags = struct.unpack(memcacheConstants.GET_RES_FMT, data[-1][:4])[0] 193 return flags, data[1], data[-1][4 + klen:] 194 195 def get(self, key, vbucket= -1): 196 """Get the value for a given key within the memcached server.""" 197 self._set_vbucket(key, vbucket) 198 parts = self._doCmd(memcacheConstants.CMD_GET, key, '') 199 200 return self.__parseGet(parts) 201 202 def send_get(self, key, vbucket= -1): 203 """ sends a get message without parsing the response """ 204 self._set_vbucket(key, vbucket) 205 opaque = self.r.randint(0, 2 ** 32) 206 self._sendCmd(memcacheConstants.CMD_GET, key, '', opaque) 207 208 def getl(self, key, exp=15, vbucket= -1): 209 """Get the value for a given key within the memcached server.""" 210 self._set_vbucket(key, vbucket) 211 parts = self._doCmd(memcacheConstants.CMD_GET_LOCKED, key, '', 212 struct.pack(memcacheConstants.GETL_PKT_FMT, exp)) 213 return self.__parseGet(parts) 214 215 def getr(self, key, vbucket= -1): 216 """Get the value for a given key within the memcached server from a replica vbucket.""" 217 self._set_vbucket(key, vbucket) 218 parts = self._doCmd(memcacheConstants.CMD_GET_REPLICA, key, '') 219 return self.__parseGet(parts, len(key)) 220 221 222 def getMeta(self, key): 223 """Get the metadata for a given key within the memcached server.""" 224 self._set_vbucket(key) 225 opaque, cas, data = self._doCmd(memcacheConstants.CMD_GET_META, key, '') 226 deleted = struct.unpack('>I', data[0:4])[0] 227 flags = struct.unpack('>I', data[4:8])[0] 228 exp = struct.unpack('>I', data[8:12])[0] 229 seqno = struct.unpack('>Q', data[12:20])[0] 230 return (deleted, flags, exp, seqno, cas) 231 232 def cas(self, key, exp, flags, oldVal, val, vbucket= -1): 233 """CAS in a new value for the given key and comparison value.""" 234 self._set_vbucket(key, vbucket) 235 self._mutate(memcacheConstants.CMD_SET, key, exp, flags, 236 oldVal, val) 237 238 def touch(self, key, exp, vbucket= -1): 239 """Touch a key in the memcached server.""" 240 self._set_vbucket(key, vbucket) 241 return self._doCmd(memcacheConstants.CMD_TOUCH, key, '', 242 struct.pack(memcacheConstants.TOUCH_PKT_FMT, exp)) 243 244 def gat(self, key, exp, vbucket= -1): 245 """Get the value for a given key and touch it within the memcached server.""" 246 self._set_vbucket(key, vbucket) 247 parts = self._doCmd(memcacheConstants.CMD_GAT, key, '', 248 struct.pack(memcacheConstants.GAT_PKT_FMT, exp)) 249 return self.__parseGet(parts) 250 251 def version(self): 252 """Get the value for a given key within the memcached server.""" 253 return self._doCmd(memcacheConstants.CMD_VERSION, '', '') 254 255 def sasl_mechanisms(self): 256 """Get the supported SASL methods.""" 257 return set(self._doCmd(memcacheConstants.CMD_SASL_LIST_MECHS, 258 '', '')[2].split(' ')) 259 260 def sasl_auth_start(self, mech, data): 261 """Start a sasl auth session.""" 262 return self._doCmd(memcacheConstants.CMD_SASL_AUTH, mech, data) 263 264 def sasl_auth_plain(self, user, password, foruser=''): 265 """Perform plain auth.""" 266 return self.sasl_auth_start('PLAIN', '\0'.join([foruser, user, password])) 267 268 def sasl_auth_cram_md5(self, user, password): 269 """Start a plan auth session.""" 270 challenge = None 271 try: 272 self.sasl_auth_start('CRAM-MD5', '') 273 except MemcachedError, e: 274 if e.status != memcacheConstants.ERR_AUTH_CONTINUE: 275 raise 276 challenge = e.msg.split(' ')[0] 277 278 dig = hmac.HMAC(password, challenge).hexdigest() 279 return self._doCmd(memcacheConstants.CMD_SASL_STEP, 'CRAM-MD5', 280 user + ' ' + dig) 281 282 def stop_persistence(self): 283 return self._doCmd(memcacheConstants.CMD_STOP_PERSISTENCE, '', '') 284 285 def start_persistence(self): 286 return self._doCmd(memcacheConstants.CMD_START_PERSISTENCE, '', '') 287 288 def set_flush_param(self, key, val): 289 print "setting flush param:", key, val 290 return self._doCmd(memcacheConstants.CMD_SET_FLUSH_PARAM, key, val) 291 292 def set_param(self, key, val, type): 293 print "setting param:", key, val 294 type = struct.pack(memcacheConstants.GET_RES_FMT, type) 295 return self._doCmd(memcacheConstants.CMD_SET_FLUSH_PARAM, key, val, type) 296 297 def start_onlineupdate(self): 298 return self._doCmd(memcacheConstants.CMD_START_ONLINEUPDATE, '', '') 299 300 def complete_onlineupdate(self): 301 return self._doCmd(memcacheConstants.CMD_COMPLETE_ONLINEUPDATE, '', '') 302 303 def revert_onlineupdate(self): 304 return self._doCmd(memcacheConstants.CMD_REVERT_ONLINEUPDATE, '', '') 305 306 def set_tap_param(self, key, val): 307 print "setting tap param:", key, val 308 return self._doCmd(memcacheConstants.CMD_SET_TAP_PARAM, key, val) 309 310 def set_vbucket_state(self, vbucket, stateName): 311 assert isinstance(vbucket, int) 312 self.vbucketId = vbucket 313 state = struct.pack(memcacheConstants.VB_SET_PKT_FMT, 314 memcacheConstants.VB_STATE_NAMES[stateName]) 315 return self._doCmd(memcacheConstants.CMD_SET_VBUCKET_STATE, '', '', state) 316 317 def get_vbucket_state(self, vbucket): 318 assert isinstance(vbucket, int) 319 self.vbucketId = vbucket 320 return self._doCmd(memcacheConstants.CMD_GET_VBUCKET_STATE, 321 str(vbucket), '') 322 323 def delete_vbucket(self, vbucket): 324 assert isinstance(vbucket, int) 325 self.vbucketId = vbucket 326 return self._doCmd(memcacheConstants.CMD_DELETE_VBUCKET, '', '') 327 328 def evict_key(self, key, vbucket= -1): 329 self._set_vbucket(key, vbucket) 330 return self._doCmd(memcacheConstants.CMD_EVICT_KEY, key, '') 331 332 def getMulti(self, keys, vbucket= -1): 333 """Get values for any available keys in the given iterable. 334 335 Returns a dict of matched keys to their values.""" 336 opaqued = dict(enumerate(keys)) 337 terminal = len(opaqued) + 10 338 # Send all of the keys in quiet 339 vbs = set() 340 for k, v in opaqued.iteritems(): 341 self._set_vbucket(v, vbucket) 342 vbs.add(self.vbucketId) 343 self._sendCmd(memcacheConstants.CMD_GETQ, v, '', k) 344 345 for vb in vbs: 346 self.vbucketId = vb 347 self._sendCmd(memcacheConstants.CMD_NOOP, '', '', terminal) 348 349 # Handle the response 350 rv = {} 351 for vb in vbs: 352 self.vbucketId = vb 353 done = False 354 while not done: 355 opaque, cas, data = self._handleSingleResponse(None) 356 if opaque != terminal: 357 rv[opaqued[opaque]] = self.__parseGet((opaque, cas, data)) 358 else: 359 done = True 360 361 return rv 362 363 def setMulti(self, exp, flags, items, vbucket= -1): 364 """Multi-set (using setq). 365 366 Give me (key, value) pairs.""" 367 368 # If this is a dict, convert it to a pair generator 369 if hasattr(items, 'iteritems'): 370 items = items.iteritems() 371 372 opaqued = dict(enumerate(items)) 373 terminal = len(opaqued) + 10 374 extra = struct.pack(SET_PKT_FMT, flags, exp) 375 376 # Send all of the keys in quiet 377 vbs = set() 378 for opaque, kv in opaqued.iteritems(): 379 self._set_vbucket(kv[0], vbucket) 380 vbs.add(self.vbucketId) 381 self._sendCmd(memcacheConstants.CMD_SETQ, kv[0], kv[1], opaque, extra) 382 383 for vb in vbs: 384 self.vbucketId = vb 385 self._sendCmd(memcacheConstants.CMD_NOOP, '', '', terminal) 386 387 # Handle the response 388 failed = [] 389 for vb in vbs: 390 self.vbucketId = vb 391 done = False 392 while not done: 393 try: 394 opaque, cas, data = self._handleSingleResponse(None) 395 done = opaque == terminal 396 except MemcachedError, e: 397 failed.append(e) 398 399 return failed 400 401 def stats(self, sub=''): 402 """Get stats.""" 403 opaque = self.r.randint(0, 2 ** 32) 404 self._sendCmd(memcacheConstants.CMD_STAT, sub, '', opaque) 405 done = False 406 rv = {} 407 while not done: 408 cmd, opaque, cas, klen, extralen, data = self._handleKeyedResponse(None) 409 if klen: 410 rv[data[0:klen]] = data[klen:] 411 else: 412 done = True 413 return rv 414 415 def noop(self): 416 """Send a noop command.""" 417 return self._doCmd(memcacheConstants.CMD_NOOP, '', '') 418 419 def delete(self, key, cas=0, vbucket= -1): 420 """Delete the value for a given key within the memcached server.""" 421 self._set_vbucket(key, vbucket) 422 return self._doCmd(memcacheConstants.CMD_DELETE, key, '', '', cas) 423 424 def flush(self, timebomb=0): 425 """Flush all storage in a memcached instance.""" 426 return self._doCmd(memcacheConstants.CMD_FLUSH, '', '', 427 struct.pack(memcacheConstants.FLUSH_PKT_FMT, timebomb)) 428 429 def bucket_select(self, name): 430 return self._doCmd(memcacheConstants.CMD_SELECT_BUCKET, name, '') 431 432 def sync_persistence(self, keyspecs): 433 payload = self._build_sync_payload(0x8, keyspecs) 434 435 print "sending sync for persistence command for the following keyspecs:", keyspecs 436 (opaque, cas, data) = self._doCmd(memcacheConstants.CMD_SYNC, "", payload) 437 return (opaque, cas, self._parse_sync_response(data)) 438 439 def sync_mutation(self, keyspecs): 440 payload = self._build_sync_payload(0x4, keyspecs) 441 442 print "sending sync for mutation command for the following keyspecs:", keyspecs 443 (opaque, cas, data) = self._doCmd(memcacheConstants.CMD_SYNC, "", payload) 444 return (opaque, cas, self._parse_sync_response(data)) 445 446 def sync_replication(self, keyspecs, numReplicas=1): 447 payload = self._build_sync_payload((numReplicas & 0x0f) << 4, keyspecs) 448 449 print "sending sync for replication command for the following keyspecs:", keyspecs 450 (opaque, cas, data) = self._doCmd(memcacheConstants.CMD_SYNC, "", payload) 451 return (opaque, cas, self._parse_sync_response(data)) 452 453 def sync_replication_or_persistence(self, keyspecs, numReplicas=1): 454 payload = self._build_sync_payload(((numReplicas & 0x0f) << 4) | 0x8, keyspecs) 455 456 print "sending sync for replication or persistence command for the " \ 457 "following keyspecs:", keyspecs 458 (opaque, cas, data) = self._doCmd(memcacheConstants.CMD_SYNC, "", payload) 459 return (opaque, cas, self._parse_sync_response(data)) 460 461 def sync_replication_and_persistence(self, keyspecs, numReplicas=1): 462 payload = self._build_sync_payload(((numReplicas & 0x0f) << 4) | 0xA, keyspecs) 463 464 print "sending sync for replication and persistence command for the " \ 465 "following keyspecs:", keyspecs 466 (opaque, cas, data) = self._doCmd(memcacheConstants.CMD_SYNC, "", payload) 467 return (opaque, cas, self._parse_sync_response(data)) 468 469 def _build_sync_payload(self, flags, keyspecs): 470 payload = struct.pack(">I", flags) 471 payload += struct.pack(">H", len(keyspecs)) 472 473 for spec in keyspecs: 474 if not isinstance(spec, dict): 475 raise TypeError("each keyspec must be a dict") 476 if not spec.has_key('vbucket'): 477 raise TypeError("missing vbucket property in keyspec") 478 if not spec.has_key('key'): 479 raise TypeError("missing key property in keyspec") 480 481 payload += struct.pack(">Q", spec.get('cas', 0)) 482 payload += struct.pack(">H", spec['vbucket']) 483 payload += struct.pack(">H", len(spec['key'])) 484 payload += spec['key'] 485 486 return payload 487 488 def _parse_sync_response(self, data): 489 keyspecs = [] 490 nkeys = struct.unpack(">H", data[0 : struct.calcsize("H")])[0] 491 offset = struct.calcsize("H") 492 493 for i in xrange(nkeys): 494 spec = {} 495 width = struct.calcsize("QHHB") 496 (spec['cas'], spec['vbucket'], keylen, eventid) = \ 497 struct.unpack(">QHHB", data[offset : offset + width]) 498 offset += width 499 spec['key'] = data[offset : offset + keylen] 500 offset += keylen 501 502 if eventid == memcacheConstants.CMD_SYNC_EVENT_PERSISTED: 503 spec['event'] = 'persisted' 504 elif eventid == memcacheConstants.CMD_SYNC_EVENT_MODIFED: 505 spec['event'] = 'modified' 506 elif eventid == memcacheConstants.CMD_SYNC_EVENT_DELETED: 507 spec['event'] = 'deleted' 508 elif eventid == memcacheConstants.CMD_SYNC_EVENT_REPLICATED: 509 spec['event'] = 'replicated' 510 elif eventid == memcacheConstants.CMD_SYNC_INVALID_KEY: 511 spec['event'] = 'invalid key' 512 elif spec['event'] == memcacheConstants.CMD_SYNC_INVALID_CAS: 513 spec['event'] = 'invalid cas' 514 else: 515 spec['event'] = eventid 516 517 keyspecs.append(spec) 518 519 return keyspecs 520 521 def restore_file(self, filename): 522 """Initiate restore of a given file.""" 523 return self._doCmd(memcacheConstants.CMD_RESTORE_FILE, filename, '', '', 0) 524 525 def restore_complete(self): 526 """Notify the server that we're done restoring.""" 527 return self._doCmd(memcacheConstants.CMD_RESTORE_COMPLETE, '', '', '', 0) 528 529 def deregister_tap_client(self, tap_name): 530 """Deregister the TAP client with a given name.""" 531 return self._doCmd(memcacheConstants.CMD_DEREGISTER_TAP_CLIENT, tap_name, '', '', 0) 532 533 def reset_replication_chain(self): 534 """Reset the replication chain.""" 535 return self._doCmd(memcacheConstants.CMD_RESET_REPLICATION_CHAIN, '', '', '', 0) 536 537 def _set_vbucket(self, key, vbucket= -1): 538 if vbucket < 0: 539 self.vbucketId = (((zlib.crc32(key)) >> 16) & 0x7fff) & (self.vbucket_count - 1) 540 else: 541 self.vbucketId = vbucket 542 543 def get_config(self): 544 """Get the config within the memcached server.""" 545 return self._doCmd(memcacheConstants.CMD_GET_CLUSTER_CONFIG, '', '') 546 547 def set_config(self, blob_conf): 548 """Set the config within the memcached server.""" 549 return self._doCmd(memcacheConstants.CMD_SET_CLUSTER_CONFIG, blob_conf, '') 550 551def error_to_str(errno): 552 if errno == 0x01: 553 return "Not found" 554 elif errno == 0x02: 555 return "Exists" 556 elif errno == 0x03: 557 return "Too big" 558 elif errno == 0x04: 559 return "Invalid" 560 elif errno == 0x05: 561 return "Not stored" 562 elif errno == 0x06: 563 return "Bad Delta" 564 elif errno == 0x07: 565 return "Not my vbucket" 566 elif errno == 0x20: 567 return "Auth error" 568 elif errno == 0x21: 569 return "Auth continue" 570 elif errno == 0x81: 571 return "Unknown Command" 572 elif errno == 0x82: 573 return "No Memory" 574 elif errno == 0x83: 575 return "Not Supported" 576 elif errno == 0x84: 577 return "Internal error" 578 elif errno == 0x85: 579 return "Busy" 580 elif errno == 0x86: 581 return "Temporary failure" 582