Annotation of researchv10no/cmd/worm/oscsi/dslib.c, revision 1.1

1.1     ! root        1: /*
        !             2: || dslib.c - library routines for /dev/scsi
        !             3: ||
        !             4: || Copyright 1988, 1989, by
        !             5: ||   Gene Dronek (Vulcan Laboratory) and
        !             6: ||   Rich Morin  (Canta Forda Computer Laboratory).
        !             7: || All rights reserved.
        !             8: */
        !             9: #ident "dslib.c: $Revision: 1.4 $"
        !            10: 
        !            11: #include <stdio.h>
        !            12: #include <sys/types.h>
        !            13: 
        !            14: #include "dslib.h"
        !            15: #ifdef aux
        !            16: #include <sys/vio.h>
        !            17: #include <sys/scsireq.h>
        !            18: #endif aux
        !            19: 
        !            20: int dsdebug=0;
        !            21: long dsreqflags;       /* flag bits always set by filldsreq */
        !            22: 
        !            23: #define min(i,j)  ( (i) < (j) ? (i) : (j) )
        !            24: 
        !            25: 
        !            26: /*
        !            27: || Startup/shutdown -----------------------------------------------
        !            28: */
        !            29: 
        !            30: static struct context *dsc[FDSIZ];
        !            31: 
        !            32: 
        !            33: /*
        !            34: || dsopen - open device, set up structures
        !            35: */
        !            36: 
        !            37: struct dsreq *
        !            38: dsopen(opath, oflags)
        !            39:   char *opath;
        !            40:   int   oflags;
        !            41: {
        !            42:     
        !            43:   struct dsreq *dsp;
        !            44:   struct context *cp;
        !            45:   int fd;
        !            46:   DSDBG(fprintf(stderr,"dsopen(%s,%x) ", opath, oflags));
        !            47: 
        !            48:   fd = open(opath, oflags);
        !            49:   if (fd < 0)                                          
        !            50:     return NULL;                       /* can't open   */
        !            51:   if (dsc[fd] != NULL)                 /* already in use */
        !            52:     ds_zot("dsopen: fd already in use");
        !            53: 
        !            54:   cp = (struct context *) calloc(1, sizeof(struct context));
        !            55:   if (cp == NULL)                                    /* can't allocate */
        !            56:     ds_zot("dsopen: can't allocate space");
        !            57:   dsc[fd] = cp;
        !            58:   cp->dsc_fd = fd;
        !            59:   dsp = &(cp->dsc_dsreq);
        !            60: 
        !            61:   dsp->ds_flags =      0;
        !            62:   dsp->ds_time =       10 * 1000;      /* 10 second default timeout */
        !            63:   dsp->ds_private =    (ulong) cp;     /* pointer back to context */
        !            64:   dsp->ds_cmdbuf =     cp->dsc_cmd;
        !            65:   dsp->ds_cmdlen =     sizeof cp->dsc_cmd;
        !            66:   dsp->ds_databuf =    0;
        !            67:   dsp->ds_datalen =    0;
        !            68:   dsp->ds_sensebuf =   cp->dsc_sense;
        !            69:   dsp->ds_senselen =   sizeof cp->dsc_sense;
        !            70:   DSDBG(fprintf(stderr,"=>cp %x, dsp %x\n", cp, dsp));
        !            71:   return dsp;
        !            72: }
        !            73: 
        !            74: 
        !            75: /*
        !            76: || dsclose - close device, release context struct.
        !            77: */
        !            78: 
        !            79: dsclose(dsp)
        !            80:   struct dsreq *dsp;
        !            81: {
        !            82:   int fd;
        !            83:   struct context *cp;
        !            84: 
        !            85:   if (dsp == NULL)
        !            86:     ds_zot("dsclose: dsp is NULL");
        !            87: 
        !            88:   cp = (struct context *)dsp->ds_private;
        !            89:   fd = getfd(dsp);
        !            90:   if ( cp == NULL )
        !            91:     ds_zot("dsclose: private is NULL");
        !            92: 
        !            93:   cfree(cp);
        !            94:   dsc[fd] = (struct context *)NULL;
        !            95:   return;
        !            96: }
        !            97: 
        !            98: 
        !            99: /*
        !           100: || Generic SCSI CCS Command functions ------------------------------------
        !           101: ||
        !           102: || dsp         dsreq pointer
        !           103: || data                data buffer pointer
        !           104: || datalen     data buffer length
        !           105: || lba         logical block address
        !           106: || vu          vendor unique bits
        !           107: */
        !           108: 
        !           109: /*
        !           110: || testunitready00 - issue group 0 "Test Unit Ready" command (0x00)
        !           111: */
        !           112: 
        !           113: testunitready00(dsp)
        !           114:   struct dsreq *dsp;
        !           115: {
        !           116:   fillg0cmd(dsp, CMDBUF(dsp), G0_TEST, 0, 0, 0, 0, 0);
        !           117:   filldsreq(dsp, 0, 0, DSRQ_READ|DSRQ_SENSE);
        !           118:   return(doscsireq(getfd(dsp), dsp));
        !           119: }
        !           120: 
        !           121: 
        !           122: /*
        !           123: || requestsense03 - issue group 0 "Request Sense" command (0x03)
        !           124: */
        !           125: 
        !           126: requestsense03(dsp, data, datalen, vu)
        !           127:   struct dsreq *dsp;
        !           128:   caddr_t data;
        !           129:   long datalen;
        !           130:   char vu;
        !           131: {
        !           132:   fillg0cmd(dsp, CMDBUF(dsp), G0_REQU, 0, 0, 0, B1(datalen), B1(vu<<6));
        !           133:   filldsreq(dsp, data, datalen, DSRQ_READ);
        !           134:   return(doscsireq(getfd(dsp), dsp));
        !           135: }
        !           136: 
        !           137: 
        !           138: /*
        !           139: || write0a - issue group 0 "Write" command (0x0a)
        !           140: */
        !           141: 
        !           142: write0a(dsp, data, datalen, lba, vu)
        !           143:   struct dsreq *dsp;
        !           144:   caddr_t data;
        !           145:   long datalen, lba;
        !           146:   char vu;
        !           147: {
        !           148:   fillg0cmd(dsp, CMDBUF(dsp), G0_WRIT, B3(lba), B1(datalen), B1(vu<<6));
        !           149:   filldsreq(dsp, data, datalen, DSRQ_READ);
        !           150:   return(doscsireq(getfd(dsp), dsp));
        !           151: }
        !           152: 
        !           153: 
        !           154: /*
        !           155: || inquiry12 - issue group 0 "Inquiry" command (0x12)
        !           156: */
        !           157: 
        !           158: inquiry12(dsp, data, datalen, vu)
        !           159:   struct dsreq *dsp;
        !           160:   caddr_t data;
        !           161:   long datalen;
        !           162:   char vu;
        !           163: {
        !           164:   fillg0cmd(dsp, CMDBUF(dsp), G0_INQU, 0, 0, 0, B1(datalen), B1(vu<<6));
        !           165:   filldsreq(dsp, data, datalen, DSRQ_READ|DSRQ_SENSE);
        !           166:   return(doscsireq(getfd(dsp), dsp));
        !           167: }
        !           168: 
        !           169: 
        !           170: /*
        !           171: || modeselect15 - issue group 0 "Mode Select" command (0x15)
        !           172: ||
        !           173: || save                0 - don't save saveable pages
        !           174: ||             1 - save saveable pages
        !           175: */
        !           176: 
        !           177: modeselect15(dsp, data, datalen, save, vu)
        !           178:   struct dsreq *dsp;
        !           179:   caddr_t data;
        !           180:   long datalen;
        !           181:   char save, vu;
        !           182: {
        !           183:   fillg0cmd(dsp, CMDBUF(dsp), G0_MSEL, save&1, 0, 0, B1(datalen), B1(vu<<6));
        !           184:   filldsreq(dsp, data, datalen, DSRQ_WRITE|DSRQ_SENSE);
        !           185:   return(doscsireq(getfd(dsp), dsp));
        !           186: }
        !           187: 
        !           188: 
        !           189: /*
        !           190: || modesense1a - issue group 0 "Mode Sense" command (0x1a)
        !           191: ||
        !           192: || pagectrl    0 - current values
        !           193: ||             1 - changeable values
        !           194: ||             2 - default values
        !           195: ||             3 - saved values
        !           196: ||
        !           197: || pagecode    0   - vendor unique
        !           198: ||             1   - error recovery
        !           199: ||             2   - disconnect/reconnect
        !           200: ||             3   - direct access dev. fmt.
        !           201: ||             4   - rigid disk geometry
        !           202: ||             5   - flexible disk
        !           203: ||             6-9 - see specific dev. types
        !           204: ||             0a  - implemented options
        !           205: ||             0b  - medium types supported
        !           206: ||             3f  - return all pages
        !           207: */
        !           208: 
        !           209: modesense1a(dsp, data, datalen, pagectrl, pagecode, vu)
        !           210:   struct dsreq *dsp;
        !           211:   caddr_t data;
        !           212:   long datalen;
        !           213:   char pagectrl, pagecode, vu;
        !           214: {
        !           215:   fillg0cmd(dsp, CMDBUF(dsp), G0_MSEN, 0x10,
        !           216:     ((pagectrl&3)<<6) | (pagecode&0x3F),
        !           217:     0, B1(datalen), B1(vu<<6));
        !           218:   filldsreq(dsp, data, datalen, DSRQ_READ|DSRQ_SENSE);
        !           219:   return(doscsireq(getfd(dsp), dsp));
        !           220: }
        !           221: 
        !           222: 
        !           223: /*
        !           224: || senddiagnostic1d - issue group 0 "Send Diagnostic" command (0x1d)
        !           225: ||
        !           226: || self                0 - run test, hold results
        !           227: ||             1 - run test, return status
        !           228: ||
        !           229: || dofl                0 - device online
        !           230: ||             1 - device offline
        !           231: ||
        !           232: || uofl                0 - unit online
        !           233: ||             1 - unit offline
        !           234: */
        !           235: 
        !           236: senddiagnostic1d(dsp, data, datalen, self, dofl, uofl, vu)
        !           237:   struct dsreq *dsp;
        !           238:   caddr_t data;
        !           239:   long datalen;
        !           240:   char self, dofl, uofl, vu;
        !           241: {
        !           242:   fillg0cmd(dsp, CMDBUF(dsp), G0_MSEN,
        !           243:     (self&1)<<2 | (dofl&1)<<1 | (uofl&1),
        !           244:     0, B2(datalen), B1(vu<<6));
        !           245:   filldsreq(dsp, data, datalen, DSRQ_READ|DSRQ_SENSE);
        !           246:   return(doscsireq(getfd(dsp), dsp));
        !           247: }
        !           248: 
        !           249: 
        !           250: /*
        !           251: || readcapacity25 - issue group 1 "Read Capacity" command (0x25)
        !           252: ||
        !           253: || pmi         0 - return last logical block, entire unit
        !           254: ||             1 - return last logical block, current track
        !           255: */
        !           256: 
        !           257: readcapacity25(dsp, data, datalen, lba, pmi, vu)
        !           258:   struct dsreq *dsp;
        !           259:   caddr_t data;
        !           260:   long datalen, lba;
        !           261:   char pmi, vu;
        !           262: {
        !           263:   fillg1cmd(dsp, CMDBUF(dsp), G1_RCAP, 0, B4(lba), 0, 0, pmi&1, B1(vu<<6));
        !           264:   filldsreq(dsp, data, datalen, DSRQ_READ|DSRQ_SENSE
        !           265:     /* |DSRQ_CTRL2 */ );
        !           266:   /* dsp->ds_time = 100;       /* often takes a while */
        !           267:   return(doscsireq(getfd(dsp), dsp));
        !           268: }
        !           269: 
        !           270: 
        !           271: /*
        !           272: || readextended28 - issue group 1 "Read Extended" command (0x28)
        !           273: */
        !           274: 
        !           275: readextended28(dsp, data, datalen, lba, vu)
        !           276:   struct dsreq *dsp;
        !           277:   caddr_t data;
        !           278:   long datalen, lba;
        !           279:   char vu;
        !           280: {
        !           281:   fillg1cmd(dsp, CMDBUF(dsp), G1_READ, 0, B4(lba), 0, B2(datalen), B1(vu<<6));
        !           282:   filldsreq(dsp, data, datalen, DSRQ_READ|DSRQ_SENSE
        !           283:     /* |DSRQ_CTRL2 */ );
        !           284:   /* dsp->ds_time = 100;       /* often takes a while */
        !           285:   return(doscsireq(getfd(dsp), dsp));
        !           286: }
        !           287: 
        !           288: 
        !           289: /*
        !           290: || writeextended2a - issue group 1 "Write Extended" command (0x2a)
        !           291: */
        !           292: 
        !           293: writeextended2a(dsp, data, datalen, lba, vu)
        !           294:   struct dsreq *dsp;
        !           295:   caddr_t data;
        !           296:   long datalen, lba;
        !           297:   char vu;
        !           298: {
        !           299:   fillg1cmd(dsp, CMDBUF(dsp), G1_WRIT, 0, B4(lba), 0, B2(datalen), B1(vu<<6));
        !           300:   filldsreq(dsp, data, datalen, DSRQ_READ|DSRQ_SENSE
        !           301:     /* |DSRQ_CTRL2 */ );
        !           302:   /* dsp->ds_time = 100;       /* often takes a while */
        !           303:   return(doscsireq(getfd(dsp), dsp));
        !           304: }
        !           305: 
        !           306: 
        !           307: /*
        !           308: || Support functions ----------------------------------------------------
        !           309: */
        !           310: 
        !           311: /*
        !           312: || fillg0cmd - Fill a Group 0 command buffer
        !           313: */
        !           314: 
        !           315: fillg0cmd(dsp, cmd, b0,b1,b2,b3,b4,b5)
        !           316:   struct dsreq *dsp;
        !           317:   uchar_t *cmd, b0,b1,b2,b3,b4,b5;
        !           318: {
        !           319:   uchar_t *c = cmd;
        !           320:   DSDBG(fprintf(stderr,"fillg0cmd(%x,%x, %02x %02x %02x %02x %02x %02x)\n",
        !           321:                dsp, cmd, b0,b1,b2,b3,b4,b5));
        !           322:   *c++ = b0, *c++ = b1, *c++ = b2, *c++ = b3, *c++ = b4, *c++ = b5;
        !           323:        
        !           324:   CMDBUF(dsp) = (caddr_t) cmd;
        !           325:   CMDLEN(dsp) = 6;
        !           326: }
        !           327: 
        !           328: 
        !           329: /*
        !           330: || fillg1cmd - Fill a Group 1 command buffer
        !           331: */
        !           332: 
        !           333: fillg1cmd(dsp, cmd, b0,b1,b2,b3,b4,b5,b6,b7,b8,b9)
        !           334:   struct dsreq *dsp;
        !           335:   uchar_t *cmd, b0,b1,b2,b3,b4,b5,b6,b7,b8,b9;
        !           336: {
        !           337:   uchar_t *c = cmd;
        !           338:   DSDBG(fprintf(stderr,
        !           339:     "fillg1cmd(%x,%x, %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x)\n",
        !           340:                dsp, cmd, b0,b1,b2,b3,b4,b5,b6,b7,b8,b9));
        !           341: 
        !           342:   *c++ = b0, *c++ = b1, *c++ = b2, *c++ = b3, *c++ = b4, *c++ = b5;
        !           343:   *c++ = b6, *c++ = b7, *c++ = b8, *c++ = b9;
        !           344:        
        !           345:   CMDBUF(dsp) = (caddr_t) cmd;
        !           346:   CMDLEN(dsp) = 10;
        !           347: }
        !           348: 
        !           349: 
        !           350: /*
        !           351: || filldsreq - Fill a dsreq structure
        !           352: */
        !           353: 
        !           354: filldsreq(dsp,data,datalen,flags)
        !           355:   struct dsreq         *dsp;
        !           356:   uchar_t              *data;
        !           357: {
        !           358:   DSDBG(fprintf(stderr,"filldsreq(%x,%x,%d,%x) cmdlen %d\n",
        !           359:                dsp,data,datalen,flags,CMDLEN(dsp)));
        !           360:   dsp->ds_flags        = flags | dsreqflags |
        !           361:          (((dsdebug&1) ? DSRQ_TRACE : 0) |
        !           362:          ((dsdebug&2) ? DSRQ_PRINT : 0));
        !           363:   dsp->ds_time = 10 * 1000;    /* default to 10 seconds */
        !           364:   dsp->ds_link = 0;
        !           365:   dsp->ds_synch        = 0;
        !           366:   dsp->ds_ret          = 0;
        !           367: 
        !           368:   DATABUF(dsp)         = (caddr_t) data;
        !           369:   DATALEN(dsp) = datalen;
        !           370: }
        !           371: 
        !           372: 
        !           373: /*
        !           374: || bprint - print array of bytes, in hex.
        !           375: */
        !           376: 
        !           377: #define hex(x) "0123456789ABCDEF" [ (x) & 0xF ]
        !           378: 
        !           379: bprint(s,n,nperline,space)
        !           380:        char *s;
        !           381: {
        !           382:        int   i, x;
        !           383:        char  *sp = (space) ? " ": "";
        !           384: 
        !           385:        for(i=0;i<n;i++)  {
        !           386:                x = s[i];
        !           387:                fprintf(stderr,((i%4==3)?"%c%c%s%s":"%c%c%s"),
        !           388:                        hex(x>>4), hex(x), sp, sp);
        !           389:                if ( i%nperline == (nperline - 1) )
        !           390:                        fprintf(stderr,"\n");
        !           391:        }
        !           392:        if ( space )
        !           393:                fprintf(stderr,"\n");
        !           394: }
        !           395: 
        !           396: 
        !           397: /*
        !           398: || doscsireq - issue scsi command, return status or -1 error.
        !           399: */
        !           400: 
        !           401: doscsireq( fd, dsp)
        !           402:   int  fd;             /* ioctl file descriptor */
        !           403:   struct dsreq *dsp;   /* devscsi request packet */
        !           404: {
        !           405:   int  cc;
        !           406:   int  retries = 4;
        !           407:   uchar_t      sbyte;
        !           408: 
        !           409:   DSDBG(fprintf(stderr,"doscsireq(%d,%x) %x ---- %s\n",fd,dsp,
        !           410:     (CMDBUF(dsp))[0],
        !           411:     ds_vtostr( (CMDBUF(dsp))[0], cmdnametab)));
        !           412: 
        !           413:   /*
        !           414:    *  loop, issuing command
        !           415:    *    until done, or further retry pointless
        !           416:    */
        !           417: 
        !           418:   while ( --retries > 0 )  {
        !           419: 
        !           420:    caddr_t sp;
        !           421: 
        !           422:     sp =  SENSEBUF(dsp);
        !           423:     DSDBG(fprintf(stderr,"cmdbuf   =  ");
        !           424:                bprint(CMDBUF(dsp),CMDLEN(dsp),16,1));
        !           425:     if ( (dsp->ds_flags & DSRQ_WRITE) )
        !           426:       DSDBG(bprint( DATABUF(dsp), min(50,DATALEN(dsp)),16,1 ));
        !           427:        
        !           428: DSDBG(fprintf(stderr,"databuf datalen %x %d\n",DATABUF(dsp), DATALEN(dsp)));
        !           429:     cc = ioctl( fd, DS_ENTER, dsp);
        !           430:     if ( cc < 0)  {
        !           431:       ds_panic(dsp, "cannot ioctl fd %d\n",fd);
        !           432:     }
        !           433:        
        !           434:        DSDBG(fprintf(stderr,"cmdlen after ioctl=%d\n",CMDLEN(dsp)));
        !           435:     DSDBG(fprintf(stderr,"ioctl=%d ret=%x %s",
        !           436:       cc, RET(dsp), 
        !           437:       RET(dsp) ? ds_vtostr(RET(dsp),dsrtnametab) : ""));
        !           438:     DSDBG(if (SENSESENT(dsp)) fprintf(stderr," sensesent=%d",
        !           439:       SENSESENT(dsp)));
        !           440: 
        !           441:     DSDBG(fprintf(stderr,
        !           442:       " cmdsent=%d datasent=%d sbyte=%x %s\n",
        !           443:       CMDSENT(dsp), DATASENT(dsp), STATUS(dsp),
        !           444:       ds_vtostr(STATUS(dsp), cmdstatustab)));
        !           445:     DSDBG(if ( FLAGS(dsp) & DSRQ_READ )
        !           446:       bprint( DATABUF(dsp), min(16*16,DATASENT(dsp)), 16,1));
        !           447: 
        !           448: #ifdef aux
        !           449:   /*
        !           450:    *  check for AUX bus-error 
        !           451:    *  we retry with poll-dma
        !           452:    */
        !           453:     if ( RET(dsp) == DSRT_AGAIN )  {
        !           454:       int n = SDC_RDPOLL|SDC_WRPOLL;
        !           455:       DSDBG(fprintf(stderr,"setting rd/wr-poll"));
        !           456:       cc = ioctl( fd, DS_SET, n);      /* set bits */
        !           457:       if ( cc != 0 )
        !           458:         return -1;
        !           459:     }
        !           460: #endif aux
        !           461: 
        !           462:     if ( RET(dsp) == DSRT_NOSEL )
        !           463:       continue;                /* retry noselect 3X */
        !           464: 
        !           465:     /* decode sense data returned */
        !           466:     if ( SENSESENT(dsp) )  {
        !           467:       DSDBG(
        !           468:         fprintf(stderr, "sense key %x - %s\n",
        !           469:           SENSEKEY(sp),
        !           470:           ds_vtostr( SENSEKEY(sp), sensekeytab));
        !           471:         bprint( SENSEBUF(dsp),
        !           472:           min(100, SENSESENT(dsp)),
        !           473:           16,1);
        !           474:       );
        !           475:     }
        !           476:     DSDBG(fprintf(stderr, "sbyte %x\n", STATUS(dsp)));
        !           477: 
        !           478:     /* decode scsi command status byte */
        !           479:     sbyte = STATUS(dsp);
        !           480:     switch (sbyte)  {
        !           481:       case 0x08:               /*  BUSY */
        !           482:       case 0x18:               /*  RESERV CONFLICT */
        !           483:        sleep(2);
        !           484:        continue;
        !           485:       case 0x00:               /*  GOOD */
        !           486:       case 0x02:               /*  CHECK CONDITION */
        !           487:       case 0x10:               /*  INTERM/GOOD */
        !           488:       default:
        !           489:        return sbyte;
        !           490:     }
        !           491:   }
        !           492:   return -1;   /* fail retry limit */
        !           493: }
        !           494: 
        !           495: 
        !           496: /*
        !           497: || opttovar - lookup option in table, return var addr (NULL if fail)
        !           498: */
        !           499: 
        !           500: int *
        !           501: opttovar( ostr, table)
        !           502:   char *ostr;
        !           503:   struct opttab{
        !           504:     char *opt;
        !           505:     int  *var;
        !           506:   } *table;
        !           507: {
        !           508:   register struct opttab *tp;
        !           509: 
        !           510:   for (tp=table; (tp->var); tp++)
        !           511:     if ( strncmp( ostr, tp->opt, 3) == 0 )
        !           512:       break;
        !           513: 
        !           514:   if ( !tp->var )
        !           515:     fprintf(stderr,"unknown option %s", ostr);
        !           516:        
        !           517:   return (tp->var);
        !           518: }
        !           519: 
        !           520: 
        !           521: /*
        !           522: || ds_vtostr - lookup value in table to return string pointer
        !           523: */
        !           524: 
        !           525: char *
        !           526: ds_vtostr( v, table)
        !           527:   long v;
        !           528:   struct vtab *table;
        !           529: {
        !           530:   register struct vtab *tp;
        !           531: 
        !           532:   for (tp=table; (tp->string); tp++)
        !           533:     if ( v == tp->val )
        !           534:       break;
        !           535:        
        !           536:   return (tp->string) ? tp->string : "";
        !           537: }
        !           538: 
        !           539: 
        !           540: /*
        !           541: || ds_panic - yelp, leave...
        !           542: */
        !           543: 
        !           544: ds_panic( fmt, v)
        !           545:   char *fmt;
        !           546:   int v;
        !           547: {
        !           548:   extern errno;
        !           549: 
        !           550:   fprintf(stderr,fmt,v);
        !           551:   fprintf(stderr,"\nerrno = %d\n",errno);
        !           552:   exit(1);
        !           553: }
        !           554: 
        !           555: 
        !           556: /*
        !           557: || ds_zot - go away, with a message.
        !           558: */
        !           559: 
        !           560: ds_zot(message)
        !           561:   char *message;
        !           562: {
        !           563:   fprintf(stderr, "%s\n", message);
        !           564:   exit(1);
        !           565: }

unix.superglobalmegacorp.com

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