1 #include "redis.h" 2 3 #include <signal.h> 4 #include <ctype.h> 5 6 void SlotToKeyAdd(robj *key); 7 void SlotToKeyDel(robj *key); 8 9 /*----------------------------------------------------------------------------- 10 * C-level DB API 11 *----------------------------------------------------------------------------*/ 12 13 robj *lookupKey(redisDb *db, robj *key) { 14 dictEntry *de = dictFind(db->dict,key->ptr); 15 if (de) { 16 robj *val = dictGetVal(de); 17 18 /* Update the access time for the aging algorithm. 19 * Don't do it if we have a saving child, as this will trigger 20 * a copy on write madness. */ 21 if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) 22 val->lru = server.lruclock; 23 return val; 24 } else { 25 return NULL; 26 } 27 } 28 29 robj *lookupKeyRead(redisDb *db, robj *key) { 30 robj *val; 31 32 expireIfNeeded(db,key); 33 val = lookupKey(db,key); 34 if (val == NULL) 35 server.stat_keyspace_misses++; 36 else 37 server.stat_keyspace_hits++; 38 return val; 39 } 40 41 robj *lookupKeyWrite(redisDb *db, robj *key) { 42 expireIfNeeded(db,key); 43 return lookupKey(db,key); 44 } 45 46 robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { 47 robj *o = lookupKeyRead(c->db, key); 48 if (!o) addReply(c,reply); 49 return o; 50 } 51 52 robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) { 53 robj *o = lookupKeyWrite(c->db, key); 54 if (!o) addReply(c,reply); 55 return o; 56 } 57 58 /* Add the key to the DB. It's up to the caller to increment the reference 59 * counte of the value if needed. 60 * 61 * The program is aborted if the key already exists. */ 62 void dbAdd(redisDb *db, robj *key, robj *val) { 63 sds copy = sdsdup(key->ptr); 64 int retval = dictAdd(db->dict, copy, val); 65 66 redisAssertWithInfo(NULL,key,retval == REDIS_OK); 67 if (server.cluster_enabled) SlotToKeyAdd(key); 68 } 69 70 /* Overwrite an existing key with a new value. Incrementing the reference 71 * count of the new value is up to the caller. 72 * This function does not modify the expire time of the existing key. 73 * 74 * The program is aborted if the key was not already present. */ 75 void dbOverwrite(redisDb *db, robj *key, robj *val) { 76 struct dictEntry *de = dictFind(db->dict,key->ptr); 77 78 redisAssertWithInfo(NULL,key,de != NULL); 79 dictReplace(db->dict, key->ptr, val); 80 } 81 82 /* High level Set operation. This function can be used in order to set 83 * a key, whatever it was existing or not, to a new object. 84 * 85 * 1) The ref count of the value object is incremented. 86 * 2) clients WATCHing for the destination key notified. 87 * 3) The expire time of the key is reset (the key is made persistent). */ 88 void setKey(redisDb *db, robj *key, robj *val) { 89 if (lookupKeyWrite(db,key) == NULL) { 90 dbAdd(db,key,val); 91 } else { 92 dbOverwrite(db,key,val); 93 } 94 incrRefCount(val); 95 removeExpire(db,key); 96 signalModifiedKey(db,key); 97 } 98 99 int dbExists(redisDb *db, robj *key) { 100 return dictFind(db->dict,key->ptr) != NULL; 101 } 102 103 /* Return a random key, in form of a Redis object. 104 * If there are no keys, NULL is returned. 105 * 106 * The function makes sure to return keys not already expired. */ 107 robj *dbRandomKey(redisDb *db) { 108 struct dictEntry *de; 109 110 while(1) { 111 sds key; 112 robj *keyobj; 113 114 de = dictGetRandomKey(db->dict); 115 if (de == NULL) return NULL; 116 117 key = dictGetKey(de); 118 keyobj = createStringObject(key,sdslen(key)); 119 if (dictFind(db->expires,key)) { 120 if (expireIfNeeded(db,keyobj)) { 121 decrRefCount(keyobj); 122 continue; /* search for another key. This expired. */ 123 } 124 } 125 return keyobj; 126 } 127 } 128 129 /* Delete a key, value, and associated expiration entry if any, from the DB */ 130 int dbDelete(redisDb *db, robj *key) { 131 /* Deleting an entry from the expires dict will not free the sds of 132 * the key, because it is shared with the main dictionary. */ 133 if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); 134 if (dictDelete(db->dict,key->ptr) == DICT_OK) { 135 if (server.cluster_enabled) SlotToKeyDel(key); 136 return 1; 137 } else { 138 return 0; 139 } 140 } 141 142 long long emptyDb() { 143 int j; 144 long long removed = 0; 145 146 for (j = 0; j < server.dbnum; j++) { 147 removed += dictSize(server.db[j].dict); 148 dictEmpty(server.db[j].dict); 149 dictEmpty(server.db[j].expires); 150 } 151 return removed; 152 } 153 154 int selectDb(redisClient *c, int id) { 155 if (id < 0 || id >= server.dbnum) 156 return REDIS_ERR; 157 c->db = &server.db[id]; 158 return REDIS_OK; 159 } 160 161 /*----------------------------------------------------------------------------- 162 * Hooks for key space changes. 163 * 164 * Every time a key in the database is modified the function 165 * signalModifiedKey() is called. 166 * 167 * Every time a DB is flushed the function signalFlushDb() is called. 168 *----------------------------------------------------------------------------*/ 169 170 void signalModifiedKey(redisDb *db, robj *key) { 171 touchWatchedKey(db,key); 172 } 173 174 void signalFlushedDb(int dbid) { 175 touchWatchedKeysOnFlush(dbid); 176 } 177 178 /*----------------------------------------------------------------------------- 179 * Type agnostic commands operating on the key space 180 *----------------------------------------------------------------------------*/ 181 182 void flushdbCommand(redisClient *c) { 183 server.dirty += dictSize(c->db->dict); 184 signalFlushedDb(c->db->id); 185 dictEmpty(c->db->dict); 186 dictEmpty(c->db->expires); 187 addReply(c,shared.ok); 188 } 189 190 void flushallCommand(redisClient *c) { 191 signalFlushedDb(-1); 192 server.dirty += emptyDb(); 193 addReply(c,shared.ok); 194 if (server.rdb_child_pid != -1) { 195 kill(server.rdb_child_pid,SIGKILL); 196 rdbRemoveTempFile(server.rdb_child_pid); 197 } 198 if (server.saveparamslen > 0) { 199 /* Normally rdbSave() will reset dirty, but we don't want this here 200 * as otherwise FLUSHALL will not be replicated nor put into the AOF. */ 201 int saved_dirty = server.dirty; 202 rdbSave(server.rdb_filename); 203 server.dirty = saved_dirty; 204 } 205 server.dirty++; 206 } 207 208 void delCommand(redisClient *c) { 209 int deleted = 0, j; 210 211 for (j = 1; j < c->argc; j++) { 212 if (dbDelete(c->db,c->argv[j])) { 213 signalModifiedKey(c->db,c->argv[j]); 214 server.dirty++; 215 deleted++; 216 } 217 } 218 addReplyLongLong(c,deleted); 219 } 220 221 void existsCommand(redisClient *c) { 222 expireIfNeeded(c->db,c->argv[1]); 223 if (dbExists(c->db,c->argv[1])) { 224 addReply(c, shared.cone); 225 } else { 226 addReply(c, shared.czero); 227 } 228 } 229 230 void selectCommand(redisClient *c) { 231 int id = atoi(c->argv[1]->ptr); 232 233 if (server.cluster_enabled && id != 0) { 234 addReplyError(c,"SELECT is not allowed in cluster mode"); 235 return; 236 } 237 if (selectDb(c,id) == REDIS_ERR) { 238 addReplyError(c,"invalid DB index"); 239 } else { 240 addReply(c,shared.ok); 241 } 242 } 243 244 void randomkeyCommand(redisClient *c) { 245 robj *key; 246 247 if ((key = dbRandomKey(c->db)) == NULL) { 248 addReply(c,shared.nullbulk); 249 return; 250 } 251 252 addReplyBulk(c,key); 253 decrRefCount(key); 254 } 255 256 void keysCommand(redisClient *c) { 257 dictIterator *di; 258 dictEntry *de; 259 sds pattern = c->argv[1]->ptr; 260 int plen = sdslen(pattern), allkeys; 261 unsigned long numkeys = 0; 262 void *replylen = addDeferredMultiBulkLength(c); 263 264 di = dictGetIterator(c->db->dict); 265 allkeys = (pattern[0] == '*' && pattern[1] == '\0'); 266 while((de = dictNext(di)) != NULL) { 267 sds key = dictGetKey(de); 268 robj *keyobj; 269 270 if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { 271 keyobj = createStringObject(key,sdslen(key)); 272 if (expireIfNeeded(c->db,keyobj) == 0) { 273 addReplyBulk(c,keyobj); 274 numkeys++; 275 } 276 decrRefCount(keyobj); 277 } 278 } 279 dictReleaseIterator(di); 280 setDeferredMultiBulkLength(c,replylen,numkeys); 281 } 282 283 void dbsizeCommand(redisClient *c) { 284 addReplyLongLong(c,dictSize(c->db->dict)); 285 } 286 287 void lastsaveCommand(redisClient *c) { 288 addReplyLongLong(c,server.lastsave); 289 } 290 291 void typeCommand(redisClient *c) { 292 robj *o; 293 char *type; 294 295 o = lookupKeyRead(c->db,c->argv[1]); 296 if (o == NULL) { 297 type = "none"; 298 } else { 299 switch(o->type) { 300 case REDIS_STRING: type = "string"; break; 301 case REDIS_LIST: type = "list"; break; 302 case REDIS_SET: type = "set"; break; 303 case REDIS_ZSET: type = "zset"; break; 304 case REDIS_HASH: type = "hash"; break; 305 default: type = "unknown"; break; 306 } 307 } 308 addReplyStatus(c,type); 309 } 310 311 void shutdownCommand(redisClient *c) { 312 int flags = 0; 313 314 if (c->argc > 2) { 315 addReply(c,shared.syntaxerr); 316 return; 317 } else if (c->argc == 2) { 318 if (!strcasecmp(c->argv[1]->ptr,"nosave")) { 319 flags |= REDIS_SHUTDOWN_NOSAVE; 320 } else if (!strcasecmp(c->argv[1]->ptr,"save")) { 321 flags |= REDIS_SHUTDOWN_SAVE; 322 } else { 323 addReply(c,shared.syntaxerr); 324 return; 325 } 326 } 327 if (prepareForShutdown(flags) == REDIS_OK) exit(0); 328 addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); 329 } 330 331 void renameGenericCommand(redisClient *c, int nx) { 332 robj *o; 333 long long expire; 334 335 /* To use the same key as src and dst is probably an error */ 336 if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { 337 addReply(c,shared.sameobjecterr); 338 return; 339 } 340 341 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) 342 return; 343 344 incrRefCount(o); 345 expire = getExpire(c->db,c->argv[1]); 346 if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { 347 if (nx) { 348 decrRefCount(o); 349 addReply(c,shared.czero); 350 return; 351 } 352 /* Overwrite: delete the old key before creating the new one with the same name. */ 353 dbDelete(c->db,c->argv[2]); 354 } 355 dbAdd(c->db,c->argv[2],o); 356 if (expire != -1) setExpire(c->db,c->argv[2],expire); 357 dbDelete(c->db,c->argv[1]); 358 signalModifiedKey(c->db,c->argv[1]); 359 signalModifiedKey(c->db,c->argv[2]); 360 server.dirty++; 361 addReply(c,nx ? shared.cone : shared.ok); 362 } 363 364 void renameCommand(redisClient *c) { 365 renameGenericCommand(c,0); 366 } 367 368 void renamenxCommand(redisClient *c) { 369 renameGenericCommand(c,1); 370 } 371 372 void moveCommand(redisClient *c) { 373 robj *o; 374 redisDb *src, *dst; 375 int srcid; 376 377 if (server.cluster_enabled) { 378 addReplyError(c,"MOVE is not allowed in cluster mode"); 379 return; 380 } 381 382 /* Obtain source and target DB pointers */ 383 src = c->db; 384 srcid = c->db->id; 385 if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { 386 addReply(c,shared.outofrangeerr); 387 return; 388 } 389 dst = c->db; 390 selectDb(c,srcid); /* Back to the source DB */ 391 392 /* If the user is moving using as target the same 393 * DB as the source DB it is probably an error. */ 394 if (src == dst) { 395 addReply(c,shared.sameobjecterr); 396 return; 397 } 398 399 /* Check if the element exists and get a reference */ 400 o = lookupKeyWrite(c->db,c->argv[1]); 401 if (!o) { 402 addReply(c,shared.czero); 403 return; 404 } 405 406 /* Return zero if the key already exists in the target DB */ 407 if (lookupKeyWrite(dst,c->argv[1]) != NULL) { 408 addReply(c,shared.czero); 409 return; 410 } 411 dbAdd(dst,c->argv[1],o); 412 incrRefCount(o); 413 414 /* OK! key moved, free the entry in the source DB */ 415 dbDelete(src,c->argv[1]); 416 server.dirty++; 417 addReply(c,shared.cone); 418 } 419 420 /*----------------------------------------------------------------------------- 421 * Expires API 422 *----------------------------------------------------------------------------*/ 423 424 int removeExpire(redisDb *db, robj *key) { 425 /* An expire may only be removed if there is a corresponding entry in the 426 * main dict. Otherwise, the key will never be freed. */ 427 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 428 return dictDelete(db->expires,key->ptr) == DICT_OK; 429 } 430 431 void setExpire(redisDb *db, robj *key, long long when) { 432 dictEntry *kde, *de; 433 434 /* Reuse the sds from the main dict in the expire dict */ 435 kde = dictFind(db->dict,key->ptr); 436 redisAssertWithInfo(NULL,key,kde != NULL); 437 de = dictReplaceRaw(db->expires,dictGetKey(kde)); 438 dictSetSignedIntegerVal(de,when); 439 } 440 441 /* Return the expire time of the specified key, or -1 if no expire 442 * is associated with this key (i.e. the key is non volatile) */ 443 long long getExpire(redisDb *db, robj *key) { 444 dictEntry *de; 445 446 /* No expire? return ASAP */ 447 if (dictSize(db->expires) == 0 || 448 (de = dictFind(db->expires,key->ptr)) == NULL) return -1; 449 450 /* The entry was found in the expire dict, this means it should also 451 * be present in the main dict (safety check). */ 452 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 453 return dictGetSignedIntegerVal(de); 454 } 455 456 /* Propagate expires into slaves and the AOF file. 457 * When a key expires in the master, a DEL operation for this key is sent 458 * to all the slaves and the AOF file if enabled. 459 * 460 * This way the key expiry is centralized in one place, and since both 461 * AOF and the master->slave link guarantee operation ordering, everything 462 * will be consistent even if we allow write operations against expiring 463 * keys. */ 464 void propagateExpire(redisDb *db, robj *key) { 465 robj *argv[2]; 466 467 argv[0] = shared.del; 468 argv[1] = key; 469 incrRefCount(argv[0]); 470 incrRefCount(argv[1]); 471 472 if (server.aof_state != REDIS_AOF_OFF) 473 feedAppendOnlyFile(server.delCommand,db->id,argv,2); 474 if (listLength(server.slaves)) 475 replicationFeedSlaves(server.slaves,db->id,argv,2); 476 477 decrRefCount(argv[0]); 478 decrRefCount(argv[1]); 479 } 480 481 int expireIfNeeded(redisDb *db, robj *key) { 482 long long when = getExpire(db,key); 483 484 if (when < 0) return 0; /* No expire for this key */ 485 486 /* Don't expire anything while loading. It will be done later. */ 487 if (server.loading) return 0; 488 489 /* If we are running in the context of a slave, return ASAP: 490 * the slave key expiration is controlled by the master that will 491 * send us synthesized DEL operations for expired keys. 492 * 493 * Still we try to return the right information to the caller, 494 * that is, 0 if we think the key should be still valid, 1 if 495 * we think the key is expired at this time. */ 496 if (server.masterhost != NULL) { 497 return mstime() > when; 498 } 499 500 /* Return when this key has not expired */ 501 if (mstime() <= when) return 0; 502 503 /* Delete the key */ 504 server.stat_expiredkeys++; 505 propagateExpire(db,key); 506 return dbDelete(db,key); 507 } 508 509 /*----------------------------------------------------------------------------- 510 * Expires Commands 511 *----------------------------------------------------------------------------*/ 512 513 /* Given an string object return true if it contains exactly the "ms" 514 * or "MS" string. This is used in order to check if the last argument 515 * of EXPIRE, EXPIREAT or TTL is "ms" to switch into millisecond input/output */ 516 int stringObjectEqualsMs(robj *a) { 517 char *arg = a->ptr; 518 return tolower(arg[0]) == 'm' && tolower(arg[1]) == 's' && arg[2] == '\0'; 519 } 520 521 void expireGenericCommand(redisClient *c, long long offset, int unit) { 522 dictEntry *de; 523 robj *key = c->argv[1], *param = c->argv[2]; 524 long long milliseconds; 525 526 if (getLongLongFromObjectOrReply(c, param, &milliseconds, NULL) != REDIS_OK) 527 return; 528 529 if (unit == UNIT_SECONDS) milliseconds *= 1000; 530 milliseconds -= offset; 531 532 de = dictFind(c->db->dict,key->ptr); 533 if (de == NULL) { 534 addReply(c,shared.czero); 535 return; 536 } 537 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 538 * should never be executed as a DEL when load the AOF or in the context 539 * of a slave instance. 540 * 541 * Instead we take the other branch of the IF statement setting an expire 542 * (possibly in the past) and wait for an explicit DEL from the master. */ 543 if (milliseconds <= 0 && !server.loading && !server.masterhost) { 544 robj *aux; 545 546 redisAssertWithInfo(c,key,dbDelete(c->db,key)); 547 server.dirty++; 548 549 /* Replicate/AOF this as an explicit DEL. */ 550 aux = createStringObject("DEL",3); 551 rewriteClientCommandVector(c,2,aux,key); 552 decrRefCount(aux); 553 signalModifiedKey(c->db,key); 554 addReply(c, shared.cone); 555 return; 556 } else { 557 long long when = mstime()+milliseconds; 558 setExpire(c->db,key,when); 559 addReply(c,shared.cone); 560 signalModifiedKey(c->db,key); 561 server.dirty++; 562 return; 563 } 564 } 565 566 void expireCommand(redisClient *c) { 567 expireGenericCommand(c,0,UNIT_SECONDS); 568 } 569 570 void expireatCommand(redisClient *c) { 571 expireGenericCommand(c,mstime(),UNIT_SECONDS); 572 } 573 574 void pexpireCommand(redisClient *c) { 575 expireGenericCommand(c,0,UNIT_MILLISECONDS); 576 } 577 578 void pexpireatCommand(redisClient *c) { 579 expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); 580 } 581 582 void ttlGenericCommand(redisClient *c, int output_ms) { 583 long long expire, ttl = -1; 584 585 expire = getExpire(c->db,c->argv[1]); 586 if (expire != -1) { 587 ttl = expire-mstime(); 588 if (ttl < 0) ttl = -1; 589 } 590 if (ttl == -1) { 591 addReplyLongLong(c,-1); 592 } else { 593 addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); 594 } 595 } 596 597 void ttlCommand(redisClient *c) { 598 ttlGenericCommand(c, 0); 599 } 600 601 void pttlCommand(redisClient *c) { 602 ttlGenericCommand(c, 1); 603 } 604 605 void persistCommand(redisClient *c) { 606 dictEntry *de; 607 608 de = dictFind(c->db->dict,c->argv[1]->ptr); 609 if (de == NULL) { 610 addReply(c,shared.czero); 611 } else { 612 if (removeExpire(c->db,c->argv[1])) { 613 addReply(c,shared.cone); 614 server.dirty++; 615 } else { 616 addReply(c,shared.czero); 617 } 618 } 619 } 620 621 /* ----------------------------------------------------------------------------- 622 * API to get key arguments from commands 623 * ---------------------------------------------------------------------------*/ 624 625 int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { 626 int j, i = 0, last, *keys; 627 REDIS_NOTUSED(argv); 628 629 if (cmd->firstkey == 0) { 630 *numkeys = 0; 631 return NULL; 632 } 633 last = cmd->lastkey; 634 if (last < 0) last = argc+last; 635 keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); 636 for (j = cmd->firstkey; j <= last; j += cmd->keystep) { 637 redisAssert(j < argc); 638 keys[i++] = j; 639 } 640 *numkeys = i; 641 return keys; 642 } 643 644 int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 645 if (cmd->getkeys_proc) { 646 return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); 647 } else { 648 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 649 } 650 } 651 652 void getKeysFreeResult(int *result) { 653 zfree(result); 654 } 655 656 int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 657 if (flags & REDIS_GETKEYS_PRELOAD) { 658 *numkeys = 0; 659 return NULL; 660 } else { 661 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 662 } 663 } 664 665 int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 666 if (flags & REDIS_GETKEYS_PRELOAD) { 667 int *keys = zmalloc(sizeof(int)); 668 *numkeys = 1; 669 keys[0] = 1; 670 return keys; 671 } else { 672 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 673 } 674 } 675 676 int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 677 int i, num, *keys; 678 REDIS_NOTUSED(cmd); 679 REDIS_NOTUSED(flags); 680 681 num = atoi(argv[2]->ptr); 682 /* Sanity check. Don't return any key if the command is going to 683 * reply with syntax error. */ 684 if (num > (argc-3)) { 685 *numkeys = 0; 686 return NULL; 687 } 688 keys = zmalloc(sizeof(int)*num); 689 for (i = 0; i < num; i++) keys[i] = 3+i; 690 *numkeys = num; 691 return keys; 692 } 693 694 /* Slot to Key API. This is used by Redis Cluster in order to obtain in 695 * a fast way a key that belongs to a specified hash slot. This is useful 696 * while rehashing the cluster. */ 697 void SlotToKeyAdd(robj *key) { 698 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 699 700 zslInsert(server.cluster.slots_to_keys,hashslot,key); 701 incrRefCount(key); 702 } 703 704 void SlotToKeyDel(robj *key) { 705 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 706 707 zslDelete(server.cluster.slots_to_keys,hashslot,key); 708 } 709 710 unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { 711 zskiplistNode *n; 712 zrangespec range; 713 int j = 0; 714 715 range.min = range.max = hashslot; 716 range.minex = range.maxex = 0; 717 718 n = zslFirstInRange(server.cluster.slots_to_keys, range); 719 while(n && n->score == hashslot && count--) { 720 keys[j++] = n->obj; 721 n = n->level[0].forward; 722 } 723 return j; 724 } 725