Annotation of XNU/bsd/hfs/hfscommon/Misc/BTreeWrapper.c, revision 1.1.1.1

1.1       root        1: /*
                      2:  * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
                      3:  *
                      4:  * @APPLE_LICENSE_HEADER_START@
                      5:  * 
                      6:  * The contents of this file constitute Original Code as defined in and
                      7:  * are subject to the Apple Public Source License Version 1.1 (the
                      8:  * "License").  You may not use this file except in compliance with the
                      9:  * License.  Please obtain a copy of the License at
                     10:  * http://www.apple.com/publicsource and read it before using this file.
                     11:  * 
                     12:  * This Original Code and all software distributed under the License are
                     13:  * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
                     14:  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
                     15:  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
                     16:  * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
                     17:  * License for the specific language governing rights and limitations
                     18:  * under the License.
                     19:  * 
                     20:  * @APPLE_LICENSE_HEADER_END@
                     21:  */
                     22: /*
                     23:        File:           BTreeWrapper.c
                     24: 
                     25:        Contains:       Interface glue for new B-tree manager.
                     26: 
                     27:        Version:        HFS Plus 1.0
                     28: 
                     29:        Copyright:      � 1996-1998 by Apple Computer, Inc., all rights reserved.
                     30: 
                     31:        File Ownership:
                     32: 
                     33:                DRI:                            Don Brady
                     34: 
                     35:                Other Contact:          Mark Day
                     36: 
                     37:                Technology:                     xxx put technology here xxx
                     38: 
                     39:        Writers:
                     40: 
                     41:                (msd)   Mark Day
                     42:                (DSH)   Deric Horn
                     43:                (djb)   Don Brady
                     44: 
                     45:        Change History (most recent first):
                     46:          <MacOSX>       8/10/98        djb             Removed all references to btcb global iterator (lastIterator).
                     47:          <MacOSX>      04/02/98        djb             GetBTreeRecord is only used for MacOS builds.
                     48:          <MacOSX>      03/31/98        djb             Sync up with final HFSVolumes.h header file.
                     49:          <CS18>          9/4/97        msd             Fix ValidHFSRecord to determine the type of B-tree by FileID,
                     50:                                                                        not record size. Add better checking for attribute b-tree keys.
                     51:          <CS17>         8/22/97        djb             Get blockReadFromDisk flag from GetCacheBlock call.
                     52:          <CS16>         8/14/97        djb             Remove reserved field checks in ValidHFSRecord (radar #1649593).
                     53:                                                                        Only call if ValidHFSRecord HFS_DIAGNOSTIC is true.
                     54:          <CS15>         8/11/97        djb             Bug 1670441. In SetEndOfForkProc, don't DebugStr if the disk is
                     55:                                                                        full.
                     56:          <CS14>         7/25/97        DSH             Pass heuristicHint to BTSearchRecord from SearchBTreeRecord.
                     57:          <CS13>         7/24/97        djb             CallBackProcs now take a file refNum instead of an FCB.
                     58:                                                                        GetBlockProc now reports if block came from disk.
                     59:          <CS12>         7/22/97        djb             Move all trace points to BTree.c file.
                     60:          <CS11>         7/21/97        djb             LogEndTime now takes an error code.
                     61:          <CS10>         7/16/97        DSH             FilesInternal.x -> FileMgrInternal.x to avoid name collision
                     62:           <CS9>         7/15/97        msd             Bug #1664103.  OpenBTree is not propagating errors from
                     63:                                                                        BTOpenPath.
                     64:           <CS8>          7/9/97        djb             Remove maxCNID check from ValidHFSRecord (radar #1649593).
                     65:           <CS7>         6/13/97        djb             In ValidHFSRecord HFSPlus threads names can be > 31 chars.
                     66:           <CS6>          6/2/97        DSH             Also flush AlternateVolumeHeader whenever Attributes or Startup
                     67:                                                                        files change size.
                     68:           <CS5>         5/28/97        msd             In ValidHFSRecord, check for attribute keys.
                     69:           <CS4>         5/19/97        djb             Move summary traces from GetBTreeRecord to BTIterateRecord.
                     70:           <CS3>          5/9/97        djb             Get in sync with new FilesInternal.i.
                     71:           <CS2>          5/7/97        djb             Add summary traces to B-tree SPI.
                     72:           <CS1>         4/24/97        djb             first checked in
                     73:         <HFS18>         4/16/97        djb             Always use new B-tree code.
                     74:         <HFS17>          4/4/97        djb             Remove clumpSize test from ValidHFSRecord.
                     75:         <HFS16>          4/4/97        djb             Get in sync with volume format changes.
                     76:         <HFS15>         3/17/97        DSH             Casting for SC, BlockProcs are now not static.
                     77:         <HFS14>          3/3/97        djb             Call trash block after closing btree!
                     78:         <HFS13>         2/19/97        djb             Add support for accessing bigger B-tree nodes.
                     79:         <HFS12>          2/6/97        msd             In CheckBTreeKey, remove test and DebugStr for parent ID being
                     80:                                                                        too big.
                     81:         <HFS11>         1/23/97        DSH             SetEndOfForkProc now calls through to update the Alternate MDB
                     82:                                                                        or VolumeHeader.
                     83:         <HFS10>         1/16/97        djb             Switched to dynamic lengths for BufferDescriptor length field in
                     84:                                                                        SearchBTreeRecord and GetBTreeRecord. Round up to clump size in
                     85:                                                                        SetEndOfForkProc.
                     86:          <HFS9>         1/15/97        djb             Don't return errors for bad file ids in key.
                     87:          <HFS8>         1/13/97        djb             Adding support for getting current record. ValidHFSRecord now
                     88:                                                                        supports variable sized thread records.
                     89:          <HFS7>          1/9/97        djb             Call CheckBTreeKey before using key length in a BlockMoveData
                     90:                                                                        call.
                     91:          <HFS6>          1/6/97        djb             Implement SetEndOfForkProc.
                     92:          <HFS5>          1/6/97        djb             Added HFS Plus support to CheckBTreeKey and ValidHFSRecord.
                     93:          <HFS4>          1/3/97        djb             Added support for large keys. Integrated latest HFSVolumesPriv.h
                     94:                                                                        changes.
                     95:          <HFS3>        12/23/96        djb             Fixed problem in SearchBTreeRecord (dataSize is an output so it
                     96:                                                                        was undefined). Added some debugging code.
                     97:          <HFS2>        12/20/96        msd             Fix OpenBTree to use the real data type for the key compare proc
                     98:                                                                        pointer (not void *). Fixed problem in SearchBTreeRecord that
                     99:                                                                        assigns a pointer to a buffer size field (forgot to dereference
                    100:                                                                        the pointer).
                    101:          <HFS1>        12/19/96        djb             first checked in
                    102: 
                    103: */
                    104: 
                    105: #include "../headers/BTreesPrivate.h"
                    106: 
                    107: 
                    108: 
                    109: 
                    110: // B-tree callbacks...
                    111: #if TARGET_API_MAC_OS8
                    112: OSStatus       GetBlockProc ( FileReference fileRefNum, UInt32 blockNum, GetBlockOptions options, BlockDescriptor *block );
                    113: OSStatus       ReleaseBlockProc ( FileReference fileRefNum, BlockDescPtr blockPtr, ReleaseBlockOptions options );
                    114: OSStatus       SetBlockSizeProc ( FileReference fileRefNum, ByteCount blockSize, ItemCount minBlockCount );
                    115: #endif
                    116: 
                    117: 
                    118: // local routines
                    119: static OSErr   CheckBTreeKey(const BTreeKey *key, const BTreeControlBlock *btcb);
                    120: static Boolean ValidHFSRecord(const void *record, const BTreeControlBlock *btcb, UInt16 recordSize);
                    121: 
                    122: 
                    123: 
                    124: 
                    125: OSErr SearchBTreeRecord(FileReference refNum, const void* key, UInt32 hint, void* foundKey, void* data, UInt16 *dataSize, UInt32 *newHint)
                    126: {
                    127:        FSBufferDescriptor       btRecord;
                    128:        BTreeIterator            searchIterator;
                    129:        FCB                                     *fcb;
                    130:        BTreeControlBlock       *btcb;
                    131:        OSStatus                         result;
                    132: 
                    133: 
                    134:        fcb = GetFileControlBlock(refNum);
                    135:        btcb = (BTreeControlBlock*) fcb->fcbBTCBPtr;
                    136: 
                    137:        btRecord.bufferAddress = data;
                    138:        btRecord.itemCount = 1;
                    139:        if ( btcb->maxKeyLength == kHFSExtentKeyMaximumLength )
                    140:                btRecord.itemSize = sizeof(HFSExtentRecord);
                    141:        else if ( btcb->maxKeyLength == kHFSPlusExtentKeyMaximumLength )
                    142:                btRecord.itemSize = sizeof(HFSPlusExtentRecord);
                    143:        else
                    144:                btRecord.itemSize = sizeof(CatalogRecord);
                    145: 
                    146:        searchIterator.hint.writeCount = 0;     // clear these out for debugging...
                    147:        searchIterator.hint.reserved1 = 0;
                    148:        searchIterator.hint.reserved2 = 0;
                    149: 
                    150:        searchIterator.hint.nodeNum = hint;
                    151:        searchIterator.hint.index = 0;
                    152: 
                    153:        result = CheckBTreeKey((BTreeKey *) key, btcb);
                    154:        ExitOnError(result);
                    155: 
                    156:        BlockMoveData(key, &searchIterator.key, CalcKeySize(btcb, (BTreeKey *) key));           //�� should we range check against maxkeylen?
                    157:        
                    158:        //      We only optimize for catalog records
                    159:        if( btRecord.itemSize == sizeof(CatalogRecord) )
                    160:        {
                    161:                UInt32  heuristicHint;
                    162:                UInt32  *cachedHint;
                    163:                Ptr             hintCachePtr = FCBTOVCB(fcb)->hintCachePtr;
                    164: 
                    165:                //      We pass a 2nd hint/guess into BTSearchRecord.  The heuristicHint is a mapping of
                    166:                //      dirID and nodeNumber, in hopes that the current search will be in the same node
                    167:                //      as the last search with the same parentID.
                    168:                result = GetMRUCacheBlock( ((HFSCatalogKey *)key)->parentID, hintCachePtr, (Ptr *)&cachedHint );
                    169:                heuristicHint = (result == noErr) ? *cachedHint : kInvalidMRUCacheKey;
                    170: 
                    171:                result = BTSearchRecord( fcb, &searchIterator, heuristicHint, &btRecord, dataSize, &searchIterator );
                    172: 
                    173:                InsertMRUCacheBlock( hintCachePtr, ((HFSCatalogKey *)key)->parentID, (Ptr) &(searchIterator.hint.nodeNum) );
                    174:        }
                    175:        else
                    176:        {
                    177:                result = BTSearchRecord( fcb, &searchIterator, kInvalidMRUCacheKey, &btRecord, dataSize, &searchIterator );
                    178:        }
                    179: 
                    180:        if (result == noErr)
                    181:        {
                    182:                *newHint = searchIterator.hint.nodeNum;
                    183: 
                    184:                result = CheckBTreeKey(&searchIterator.key, btcb);
                    185:                ExitOnError(result);
                    186: 
                    187:                BlockMoveData(&searchIterator.key, foundKey, CalcKeySize(btcb, &searchIterator.key));   //�� warning, this could overflow user's buffer!!!
                    188: 
                    189:                if ( DEBUG_BUILD && !ValidHFSRecord(data, btcb, *dataSize) )
                    190:                        DebugStr("\pSearchBTreeRecord: bad record?");
                    191:        }
                    192: 
                    193: ErrorExit:
                    194: 
                    195:        return result;
                    196: }
                    197: 
                    198: 
                    199: 
                    200: OSErr InsertBTreeRecord(FileReference refNum, void* key, void* data, UInt16 dataSize, UInt32 *newHint)
                    201: {
                    202:        FSBufferDescriptor      btRecord;
                    203:        BTreeIterator           iterator;
                    204:        FCB                                     *fcb;
                    205:        BTreeControlBlock       *btcb;
                    206:        OSStatus                        result;
                    207:        
                    208: 
                    209:        fcb = GetFileControlBlock(refNum);
                    210:        btcb = (BTreeControlBlock*) fcb->fcbBTCBPtr;
                    211: 
                    212:        btRecord.bufferAddress = data;
                    213:        btRecord.itemSize = dataSize;
                    214:        btRecord.itemCount = 1;
                    215: 
                    216:        iterator.hint.nodeNum = 0;                      // no hint
                    217: 
                    218:        result = CheckBTreeKey((BTreeKey *) key, btcb);
                    219:        ExitOnError(result);
                    220: 
                    221:        BlockMoveData(key, &iterator.key, CalcKeySize(btcb, (BTreeKey *) key)); //�� should we range check against maxkeylen?
                    222: 
                    223:        if ( DEBUG_BUILD && !ValidHFSRecord(data, btcb, dataSize) )
                    224:                DebugStr("\pInsertBTreeRecord: bad record?");
                    225: 
                    226:        result = BTInsertRecord( fcb, &iterator, &btRecord, dataSize );
                    227: 
                    228:        *newHint = iterator.hint.nodeNum;
                    229:        
                    230: ErrorExit:
                    231: 
                    232:        return result;
                    233: }
                    234: 
                    235: 
                    236: OSErr DeleteBTreeRecord(FileReference refNum, void* key)
                    237: {
                    238:        BTreeIterator           iterator;
                    239:        FCB                                     *fcb;
                    240:        BTreeControlBlock       *btcb;
                    241:        OSStatus                        result;
                    242:        
                    243: 
                    244:        fcb = GetFileControlBlock(refNum);
                    245:        btcb = (BTreeControlBlock*) fcb->fcbBTCBPtr;
                    246:        
                    247:        iterator.hint.nodeNum = 0;                      // no hint
                    248: 
                    249:        result = CheckBTreeKey((BTreeKey *) key, btcb);
                    250:        ExitOnError(result);
                    251: 
                    252:        BlockMoveData(key, &iterator.key, CalcKeySize(btcb, (BTreeKey *) key)); //�� should we range check against maxkeylen?
                    253: 
                    254:        result = BTDeleteRecord( fcb, &iterator );
                    255: 
                    256: ErrorExit:
                    257: 
                    258:        return result;
                    259: }
                    260: 
                    261: 
                    262: OSErr ReplaceBTreeRecord(FileReference refNum, const void* key, UInt32 hint, void *newData, UInt16 dataSize, UInt32 *newHint)
                    263: {
                    264:        FSBufferDescriptor      btRecord;
                    265:        BTreeIterator           iterator;
                    266:        FCB                                     *fcb;
                    267:        BTreeControlBlock       *btcb;
                    268:        OSStatus                        result;
                    269: 
                    270: 
                    271:        fcb = GetFileControlBlock(refNum);
                    272:        btcb = (BTreeControlBlock*) fcb->fcbBTCBPtr;
                    273: 
                    274:        btRecord.bufferAddress = newData;
                    275:        btRecord.itemSize = dataSize;
                    276:        btRecord.itemCount = 1;
                    277: 
                    278:        iterator.hint.nodeNum = hint;
                    279: 
                    280:        result = CheckBTreeKey((BTreeKey *) key, btcb);
                    281:        ExitOnError(result);
                    282: 
                    283:        BlockMoveData(key, &iterator.key, CalcKeySize(btcb, (BTreeKey *) key));         //�� should we range check against maxkeylen?
                    284: 
                    285:        if ( DEBUG_BUILD && !ValidHFSRecord(newData, btcb, dataSize) )
                    286:                DebugStr("\pReplaceBTreeRecord: bad record?");
                    287: 
                    288:        result = BTReplaceRecord( fcb, &iterator, &btRecord, dataSize );
                    289: 
                    290:        *newHint = iterator.hint.nodeNum;
                    291: 
                    292:        //���do we need to invalidate the iterator?
                    293: 
                    294: ErrorExit:
                    295: 
                    296:        return result;
                    297: }
                    298: 
                    299: 
                    300: 
                    301: static OSErr CheckBTreeKey(const BTreeKey *key, const BTreeControlBlock *btcb)
                    302: {
                    303:        UInt16  keyLen;
                    304:        
                    305:        if ( btcb->attributes & kBTBigKeysMask )
                    306:                keyLen = key->length16;
                    307:        else
                    308:                keyLen = key->length8;
                    309: 
                    310:        if ( (keyLen < 6) || (keyLen > btcb->maxKeyLength) )
                    311:        {
                    312:                if ( DEBUG_BUILD )
                    313:                        DebugStr("\pCheckBTreeKey: bad key length!");
                    314:                return fsBTInvalidKeyLengthErr;
                    315:        }
                    316:        
                    317:        return noErr;
                    318: }
                    319: 
                    320: 
                    321: static Boolean ValidHFSRecord(const void *record, const BTreeControlBlock *btcb, UInt16 recordSize)
                    322: {
                    323:        UInt32                  cNodeID;
                    324:        
                    325:        if ( btcb->maxKeyLength == kHFSExtentKeyMaximumLength )
                    326:        {
                    327:                return ( recordSize == sizeof(HFSExtentRecord) );
                    328:        }
                    329:        else if (btcb->maxKeyLength == kHFSPlusExtentKeyMaximumLength )
                    330:        {
                    331:                return ( recordSize == sizeof(HFSPlusExtentRecord) );
                    332:        }
                    333:        else // Catalog record
                    334:        {
                    335:                CatalogRecord *catalogRecord = (CatalogRecord*) record;
                    336: 
                    337:                switch(catalogRecord->recordType)
                    338:                {
                    339:                        case kHFSFolderRecord:
                    340:                        {
                    341:                                if ( recordSize != sizeof(HFSCatalogFolder) )
                    342:                                        return false;
                    343:                                if ( catalogRecord->hfsFolder.flags != 0 )
                    344:                                        return false;
                    345:                                if ( catalogRecord->hfsFolder.valence > 0x7FFF )
                    346:                                        return false;
                    347:                                        
                    348:                                cNodeID = catalogRecord->hfsFolder.folderID;
                    349:        
                    350:                                if ( (cNodeID == 0) || (cNodeID < 16 && cNodeID > 2) )
                    351:                                        return false;
                    352:                        }
                    353:                        break;
                    354: 
                    355:                        case kHFSPlusFolderRecord:
                    356:                        {
                    357:                                if ( recordSize != sizeof(HFSPlusCatalogFolder) )
                    358:                                        return false;
                    359:                                if ( catalogRecord->hfsPlusFolder.flags != 0 )
                    360:                                        return false;
                    361:                                if ( catalogRecord->hfsPlusFolder.valence > 0x7FFF )
                    362:                                        return false;
                    363:                                        
                    364:                                cNodeID = catalogRecord->hfsPlusFolder.folderID;
                    365:        
                    366:                                if ( (cNodeID == 0) || (cNodeID < 16 && cNodeID > 2) )
                    367:                                        return false;
                    368:                        }
                    369:                        break;
                    370:        
                    371:                        case kHFSFileRecord:
                    372:                        {
                    373: //                             UInt16                                  i;
                    374:                                HFSExtentDescriptor     *dataExtent;
                    375:                                HFSExtentDescriptor     *rsrcExtent;
                    376:                                
                    377:                                if ( recordSize != sizeof(HFSCatalogFile) )
                    378:                                        return false;                                                           
                    379:                                if ( (catalogRecord->hfsFile.flags & ~(0x83)) != 0 )
                    380:                                        return false;
                    381:                                        
                    382:                                cNodeID = catalogRecord->hfsFile.fileID;
                    383:                                
                    384:                                if ( cNodeID < 16 )
                    385:                                        return false;
                    386:                
                    387:                                // make sure 0 � LEOF � PEOF for both forks
                    388:                                
                    389:                                if ( catalogRecord->hfsFile.dataLogicalSize < 0 )
                    390:                                        return false;
                    391:                                if ( catalogRecord->hfsFile.dataPhysicalSize < catalogRecord->hfsFile.dataLogicalSize )
                    392:                                        return false;
                    393:                                if ( catalogRecord->hfsFile.rsrcLogicalSize < 0 )
                    394:                                        return false;
                    395:                                if ( catalogRecord->hfsFile.rsrcPhysicalSize < catalogRecord->hfsFile.rsrcLogicalSize )
                    396:                                        return false;
                    397:                
                    398:                                dataExtent = (HFSExtentDescriptor*) &catalogRecord->hfsFile.dataExtents;
                    399:                                rsrcExtent = (HFSExtentDescriptor*) &catalogRecord->hfsFile.rsrcExtents;
                    400:        
                    401: #if 0
                    402:                                for (i = 0; i < kHFSExtentDensity; ++i)
                    403:                                {
                    404:                                        if ( (dataExtent[i].blockCount > 0) && (dataExtent[i].startBlock == 0) )
                    405:                                                return false;
                    406:                                        if ( (rsrcExtent[i].blockCount > 0) && (rsrcExtent[i].startBlock == 0) )
                    407:                                                return false;
                    408:                                }
                    409: #endif
                    410:                        }
                    411:                        break;
                    412:        
                    413:                        case kHFSPlusFileRecord:
                    414:                        {
                    415: //                             UInt16                                  i;
                    416:                                HFSPlusExtentDescriptor *dataExtent;
                    417:                                HFSPlusExtentDescriptor *rsrcExtent;
                    418:                                
                    419:                                if ( recordSize != sizeof(HFSPlusCatalogFile) )
                    420:                                        return false;                                                           
                    421:                                if ( (catalogRecord->hfsPlusFile.flags & ~(0x83)) != 0 )
                    422:                                        return false;
                    423:                                        
                    424:                                cNodeID = catalogRecord->hfsPlusFile.fileID;
                    425:                                
                    426:                                if ( cNodeID < 16 )
                    427:                                        return false;
                    428:                
                    429:                                // make sure 0 � LEOF � PEOF for both forks
                    430:                
                    431:                                dataExtent = (HFSPlusExtentDescriptor*) &catalogRecord->hfsPlusFile.dataFork.extents;
                    432:                                rsrcExtent = (HFSPlusExtentDescriptor*) &catalogRecord->hfsPlusFile.resourceFork.extents;
                    433:        
                    434: #if 0
                    435:                                for (i = 0; i < kHFSPlusExtentDensity; ++i)
                    436:                                {
                    437:                                        if ( (dataExtent[i].blockCount > 0) && (dataExtent[i].startBlock == 0) )
                    438:                                                return false;
                    439:                                        if ( (rsrcExtent[i].blockCount > 0) && (rsrcExtent[i].startBlock == 0) )
                    440:                                                return false;
                    441:                                }
                    442: #endif
                    443:                        }
                    444:                        break;
                    445: 
                    446:                        case kHFSFolderThreadRecord:
                    447:                        case kHFSFileThreadRecord:
                    448:                        {
                    449:                                if ( recordSize != sizeof(HFSCatalogThread) )
                    450:                                        return false;
                    451:        
                    452:                                cNodeID = catalogRecord->hfsThread.parentID;
                    453:                                if ( (cNodeID == 0) || (cNodeID < 16 && cNodeID > 2) )
                    454:                                        return false;
                    455:                                                        
                    456:                                if ( (catalogRecord->hfsThread.nodeName[0] == 0) ||
                    457:                                         (catalogRecord->hfsThread.nodeName[0] > 31) )
                    458:                                        return false;
                    459:                        }
                    460:                        break;
                    461:                
                    462:                        case kHFSPlusFolderThreadRecord:
                    463:                        case kHFSPlusFileThreadRecord:
                    464:                        {
                    465:                                if ( recordSize > sizeof(HFSPlusCatalogThread) || recordSize < (sizeof(HFSPlusCatalogThread) - sizeof(HFSUniStr255)))
                    466:                                        return false;
                    467:        
                    468:                                cNodeID = catalogRecord->hfsPlusThread.parentID;
                    469:                                if ( (cNodeID == 0) || (cNodeID < 16 && cNodeID > 2) )
                    470:                                        return false;
                    471:                                                        
                    472:                                if ( (catalogRecord->hfsPlusThread.nodeName.length == 0) ||
                    473:                                         (catalogRecord->hfsPlusThread.nodeName.length > 255) )
                    474:                                        return false;
                    475:                        }
                    476:                        break;
                    477: 
                    478:                        default:
                    479:                                return false;
                    480:                }
                    481:        }
                    482:        
                    483:        return true;    // record appears to be OK
                    484: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.