Annotation of linux/fs/msdos/namei.c, revision 1.1

1.1     ! root        1: /*
        !             2:  *  linux/fs/msdos/namei.c
        !             3:  *
        !             4:  *  Written 1992 by Werner Almesberger
        !             5:  */
        !             6: 
        !             7: #include <asm/segment.h>
        !             8: 
        !             9: #include <linux/sched.h>
        !            10: #include <linux/msdos_fs.h>
        !            11: #include <linux/kernel.h>
        !            12: #include <linux/errno.h>
        !            13: #include <linux/string.h>
        !            14: #include <linux/stat.h>
        !            15: 
        !            16: /* MS-DOS "device special files" */
        !            17: 
        !            18: static char *reserved_names[] = {
        !            19:     "CON     ","PRN     ","NUL     ","AUX     ",
        !            20:     "LPT1    ","LPT2    ","LPT3    ","LPT4    ",
        !            21:     "COM1    ","COM2    ","COM3    ","COM4    ",
        !            22:     NULL };
        !            23: 
        !            24: 
        !            25: /* Formats an MS-DOS file name. Rejects invalid names. */
        !            26: 
        !            27: static int msdos_format_name(char conv,const char *name,int len,char *res)
        !            28: {
        !            29:        char *walk,**reserved;
        !            30:        char c;
        !            31:        int space;
        !            32: 
        !            33:        if (get_fs_byte(name) == DELETED_FLAG) return -EINVAL;
        !            34:        if (get_fs_byte(name) == '.' && (len == 1 || (len == 2 &&
        !            35:            get_fs_byte(name+1) == '.'))) {
        !            36:                memset(res+1,' ',10);
        !            37:                while (len--) *res++ = '.';
        !            38:                return 0;
        !            39:        }
        !            40:        space = 0; /* to make GCC happy */
        !            41:        c = 0;
        !            42:        for (walk = res; len && walk-res < 8; walk++) {
        !            43:                c = get_fs_byte(name++);
        !            44:                len--;
        !            45:                if (c == ' ' && conv != 'r') return -EINVAL;
        !            46:                if (c >= 'A' && c <= 'Z') {
        !            47:                        if (conv != 'r') return -EINVAL;
        !            48:                        c += 32;
        !            49:                }
        !            50:                if (c < ' ' || c == ':' || c == '\\') return -EINVAL;
        !            51:                if (c == '.') break;
        !            52:                space = c == ' ';
        !            53:                *walk = c >= 'a' && c <= 'z' ? c-32 : c;
        !            54:        }
        !            55:        if (space) return -EINVAL;
        !            56:        if (conv == 's' && len && c != '.') {
        !            57:                c = get_fs_byte(name++);
        !            58:                len--;
        !            59:                if (c != '.') return -EINVAL;
        !            60:        }
        !            61:        while (c != '.' && len--) c = get_fs_byte(name++);
        !            62:        if (walk == res) return -EINVAL;
        !            63:        if (c == '.') {
        !            64:                while (walk-res < 8) *walk++ = ' ';
        !            65:                while (len > 0 && walk-res < MSDOS_NAME) {
        !            66:                        c = get_fs_byte(name++);
        !            67:                        len--;
        !            68:                        if (c == ' ' && conv != 'r') return -EINVAL;
        !            69:                        if (c < ' ' || c == ':' || c == '\\' || c == '.')
        !            70:                                return -EINVAL;
        !            71:                        if (c >= 'A' && c <= 'Z') {
        !            72:                                if (conv != 'r') return -EINVAL;
        !            73:                                c += 32;
        !            74:                        }
        !            75:                        space = c == ' ';
        !            76:                        *walk++ = c >= 'a' && c <= 'z' ? c-32 : c;
        !            77:                }
        !            78:                if (space) return -EINVAL;
        !            79:                if (conv == 's' && len) return -EINVAL;
        !            80:        }
        !            81:        while (walk-res < MSDOS_NAME) *walk++ = ' ';
        !            82:        for (reserved = reserved_names; *reserved; reserved++)
        !            83:                if (!strncmp(res,*reserved,8)) return -EINVAL;
        !            84:        return 0;
        !            85: }
        !            86: 
        !            87: 
        !            88: /* Locates a directory entry. */
        !            89: 
        !            90: static int msdos_find(struct inode *dir,const char *name,int len,
        !            91:     struct buffer_head **bh,struct msdos_dir_entry **de,int *ino)
        !            92: {
        !            93:        char msdos_name[MSDOS_NAME];
        !            94:        int res;
        !            95: 
        !            96:        if ((res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,name,len,
        !            97:            msdos_name)) < 0) return res;
        !            98:        return msdos_scan(dir,msdos_name,bh,de,ino);
        !            99: }
        !           100: 
        !           101: 
        !           102: int msdos_lookup(struct inode *dir,const char *name,int len,
        !           103:     struct inode **result)
        !           104: {
        !           105:        int ino,res;
        !           106:        struct msdos_dir_entry *de;
        !           107:        struct buffer_head *bh;
        !           108:        struct inode *next;
        !           109: 
        !           110:        *result = NULL;
        !           111:        if (!dir) return -ENOENT;
        !           112:        if (!S_ISDIR(dir->i_mode)) {
        !           113:                iput(dir);
        !           114:                return -ENOENT;
        !           115:        }
        !           116:        if (len == 1 && get_fs_byte(name) == '.') {
        !           117:                *result = dir;
        !           118:                return 0;
        !           119:        }
        !           120:        if (len == 2 && get_fs_byte(name) == '.' && get_fs_byte(name+1) == '.')
        !           121:            {
        !           122:                ino = msdos_parent_ino(dir,0);
        !           123:                iput(dir);
        !           124:                if (ino < 0) return ino;
        !           125:                if (!(*result = iget(dir->i_dev,ino))) return -EACCES;
        !           126:                return 0;
        !           127:        }
        !           128:        if ((res = msdos_find(dir,name,len,&bh,&de,&ino)) < 0) {
        !           129:                iput(dir);
        !           130:                return res;
        !           131:        }
        !           132:        if (bh) brelse(bh);
        !           133: /* printk("lookup: ino=%d\r\n",ino); */
        !           134:        if (!(*result = iget(dir->i_dev,ino))) {
        !           135:                iput(dir);
        !           136:                return -EACCES;
        !           137:        }
        !           138:        if ((*result)->i_data[D_BUSY]) { /* mkdir in progress */
        !           139:                iput(*result);
        !           140:                iput(dir);
        !           141:                return -ENOENT;
        !           142:        }
        !           143:        while ((*result)->i_data[D_OLD]) {
        !           144:                next = (struct inode *) ((*result)->i_data[D_OLD]);
        !           145:                iput(*result);
        !           146:                if (!(*result = iget(next->i_dev,next->i_ino)))
        !           147:                        panic("msdos_lookup: Can't happen");
        !           148:        }
        !           149:        iput(dir);
        !           150:        return 0;
        !           151: }
        !           152: 
        !           153: 
        !           154: /* Creates a directory entry (name is already formatted). */
        !           155: 
        !           156: static int msdos_create_entry(struct inode *dir,char *name,int is_dir,
        !           157:     struct inode **result)
        !           158: {
        !           159:        struct buffer_head *bh;
        !           160:        struct msdos_dir_entry *de;
        !           161:        int res,ino;
        !           162: 
        !           163:        if ((res = msdos_scan(dir,NULL,&bh,&de,&ino)) < 0) {
        !           164:                if (dir->i_ino == MSDOS_ROOT_INO) return -ENOSPC;
        !           165:                if ((res = msdos_add_cluster(dir)) < 0) return res;
        !           166:                if ((res = msdos_scan(dir,NULL,&bh,&de,&ino)) < 0) return res;
        !           167:        }
        !           168:        memcpy(de->name,name,MSDOS_NAME);
        !           169:        de->attr = is_dir ? ATTR_DIR : ATTR_ARCH;
        !           170:        de->start = 0;
        !           171:        date_unix2dos(CURRENT_TIME,&de->time,&de->date);
        !           172:        de->size = 0;
        !           173:        bh->b_dirt = 1;
        !           174:        if (*result = iget(dir->i_dev,ino)) msdos_read_inode(*result);
        !           175:        brelse(bh);
        !           176:        if (!*result) return -EIO;
        !           177:        (*result)->i_mtime = (*result)->i_atime = (*result)->i_ctime =
        !           178:            CURRENT_TIME;
        !           179:        (*result)->i_dirt = 1;
        !           180:        return 0;
        !           181: }
        !           182: 
        !           183: 
        !           184: int msdos_create(struct inode *dir,const char *name,int len,int mode,
        !           185:        struct inode **result)
        !           186: {
        !           187:        struct buffer_head *bh;
        !           188:        struct msdos_dir_entry *de;
        !           189:        char msdos_name[MSDOS_NAME];
        !           190:        int ino,res;
        !           191: 
        !           192:        if (!dir) return -ENOENT;
        !           193:        if ((res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,name,len,
        !           194:            msdos_name)) < 0) {
        !           195:                iput(dir);
        !           196:                return res;
        !           197:        }
        !           198:        lock_creation();
        !           199:        if (msdos_scan(dir,msdos_name,&bh,&de,&ino) >= 0) {
        !           200:                unlock_creation();
        !           201:                brelse(bh);
        !           202:                iput(dir);
        !           203:                return -EEXIST;
        !           204:        }
        !           205:        res = msdos_create_entry(dir,msdos_name,S_ISDIR(mode),result);
        !           206:        unlock_creation();
        !           207:        iput(dir);
        !           208:        return res;
        !           209: }
        !           210: 
        !           211: 
        !           212: int msdos_mkdir(struct inode *dir,const char *name,int len,int mode)
        !           213: {
        !           214:        struct buffer_head *bh;
        !           215:        struct msdos_dir_entry *de;
        !           216:        struct inode *inode,*dot;
        !           217:        char msdos_name[MSDOS_NAME];
        !           218:        int ino,res;
        !           219: 
        !           220:        if ((res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,name,len,
        !           221:            msdos_name)) < 0) {
        !           222:                iput(dir);
        !           223:                return res;
        !           224:        }
        !           225:        lock_creation();
        !           226:        if (msdos_scan(dir,msdos_name,&bh,&de,&ino) >= 0) {
        !           227:                unlock_creation();
        !           228:                brelse(bh);
        !           229:                iput(dir);
        !           230:                return -EEXIST;
        !           231:        }
        !           232:        if ((res = msdos_create_entry(dir,msdos_name,1,&inode)) < 0) {
        !           233:                unlock_creation();
        !           234:                iput(dir);
        !           235:                return res;
        !           236:        }
        !           237:        inode->i_data[D_BUSY] = 1; /* prevent lookups */
        !           238:        if ((res = msdos_add_cluster(inode)) < 0) goto mkdir_error;
        !           239:        if ((res = msdos_create_entry(inode,MSDOS_DOT,1,&dot)) < 0)
        !           240:                goto mkdir_error;
        !           241:        dot->i_size = inode->i_size;
        !           242:        dot->i_data[D_START] = inode->i_data[D_START];
        !           243:        dot->i_dirt = 1;
        !           244:        iput(dot);
        !           245:        if ((res = msdos_create_entry(inode,MSDOS_DOTDOT,1,&dot)) < 0)
        !           246:                goto mkdir_error;
        !           247:        unlock_creation();
        !           248:        dot->i_size = dir->i_size;
        !           249:        dot->i_data[D_START] = dir->i_data[D_START];
        !           250:        dot->i_dirt = 1;
        !           251:        inode->i_data[D_BUSY] = 0;
        !           252:        iput(dot);
        !           253:        iput(inode);
        !           254:        iput(dir);
        !           255:        return 0;
        !           256: mkdir_error:
        !           257:        iput(inode);
        !           258:        if (msdos_rmdir(dir,name,len) < 0) panic("rmdir in mkdir failed");
        !           259:        unlock_creation();
        !           260:        return res;
        !           261: }
        !           262: 
        !           263: 
        !           264: int msdos_rmdir(struct inode *dir,const char *name,int len)
        !           265: {
        !           266:        int res,ino,pos;
        !           267:        struct buffer_head *bh,*dbh;
        !           268:        struct msdos_dir_entry *de,*dde;
        !           269:        struct inode *inode;
        !           270: 
        !           271:        bh = NULL;
        !           272:        inode = NULL;
        !           273:        res = -EINVAL;
        !           274:        if (len == 1 && get_fs_byte(name) == '.') goto rmdir_done;
        !           275:        if ((res = msdos_find(dir,name,len,&bh,&de,&ino)) < 0) goto rmdir_done;
        !           276:        res = -ENOENT;
        !           277:        if (!(inode = iget(dir->i_dev,ino))) goto rmdir_done;
        !           278:        res = -ENOTDIR;
        !           279:        if (!S_ISDIR(inode->i_mode)) goto rmdir_done;
        !           280:        res = -EBUSY;
        !           281:        if (dir->i_dev != inode->i_dev || dir == inode) goto rmdir_done;
        !           282:        if (inode->i_count > 1) goto rmdir_done;
        !           283:        if (inode->i_data[D_START]) { /* may be zero in mkdir */
        !           284:                res = -ENOTEMPTY;
        !           285:                pos = 0;
        !           286:                dbh = NULL;
        !           287:                while (msdos_get_entry(inode,&pos,&dbh,&dde) > -1)
        !           288:                        if (dde->name[0] && ((unsigned char *) dde->name)[0] !=
        !           289:                            DELETED_FLAG && strncmp(dde->name,MSDOS_DOT,
        !           290:                            MSDOS_NAME) && strncmp(dde->name,MSDOS_DOTDOT,
        !           291:                            MSDOS_NAME)) goto rmdir_done;
        !           292:                if (dbh) brelse(dbh);
        !           293:        }
        !           294:        inode->i_nlink = 0;
        !           295:        dir->i_mtime = CURRENT_TIME;
        !           296:        inode->i_dirt = dir->i_dirt = 1;
        !           297:        de->name[0] = DELETED_FLAG;
        !           298:        bh->b_dirt = 1;
        !           299:        res = 0;
        !           300: rmdir_done:
        !           301:        brelse(bh);
        !           302:        iput(dir);
        !           303:        iput(inode);
        !           304:        return res;
        !           305: }
        !           306: 
        !           307: 
        !           308: int msdos_unlink(struct inode *dir,const char *name,int len)
        !           309: {
        !           310:        int res,ino;
        !           311:        struct buffer_head *bh;
        !           312:        struct msdos_dir_entry *de;
        !           313:        struct inode *inode;
        !           314: 
        !           315:        bh = NULL;
        !           316:        inode = NULL;
        !           317:        if ((res = msdos_find(dir,name,len,&bh,&de,&ino)) < 0)
        !           318:                goto unlink_done;
        !           319:        if (!(inode = iget(dir->i_dev,ino))) {
        !           320:                res = -ENOENT;
        !           321:                goto unlink_done;
        !           322:        }
        !           323:        if (!S_ISREG(inode->i_mode)) {
        !           324:                res = -EPERM;
        !           325:                goto unlink_done;
        !           326:        }
        !           327:        inode->i_nlink = 0;
        !           328:        inode->i_data[D_BUSY] = 1;
        !           329:        inode->i_dirt = 1;
        !           330:        de->name[0] = DELETED_FLAG;
        !           331:        bh->b_dirt = 1;
        !           332: unlink_done:
        !           333:        brelse(bh);
        !           334:        iput(inode);
        !           335:        iput(dir);
        !           336:        return res;
        !           337: }
        !           338: 
        !           339: 
        !           340: static int rename_same_dir(struct inode *old_dir,char *old_name,
        !           341:     struct inode *new_dir,char *new_name,struct buffer_head *old_bh,
        !           342:     struct msdos_dir_entry *old_de,int old_ino)
        !           343: {
        !           344:        struct buffer_head *new_bh;
        !           345:        struct msdos_dir_entry *new_de;
        !           346:        struct inode *new_inode,*old_inode;
        !           347:        int new_ino;
        !           348:        int exists;
        !           349: 
        !           350:        if (!strncmp(old_name,new_name,MSDOS_NAME)) return 0;
        !           351:        exists = msdos_scan(new_dir,new_name,&new_bh,&new_de,&new_ino) >= 0;
        !           352:        if (*(unsigned char *) old_de->name == DELETED_FLAG) {
        !           353:                if (exists) brelse(new_bh);
        !           354:                return -ENOENT;
        !           355:        }
        !           356:        if (exists) {
        !           357:                if (!(new_inode = iget(new_dir->i_dev,new_ino))) {
        !           358:                        brelse(new_bh);
        !           359:                        return -EIO;
        !           360:                }
        !           361:                if (S_ISDIR(new_inode->i_mode)) {
        !           362:                        iput(new_inode);
        !           363:                        brelse(new_bh);
        !           364:                        return -EPERM;
        !           365:                }
        !           366:                new_inode->i_nlink = 0;
        !           367:                new_inode->i_data[D_BUSY] = 1;
        !           368:                new_inode->i_dirt = 1;
        !           369:                new_de->name[0] = DELETED_FLAG;
        !           370:                new_bh->b_dirt = 1;
        !           371:                iput(new_inode);
        !           372:                brelse(new_bh);
        !           373:        }
        !           374:        memcpy(old_de->name,new_name,MSDOS_NAME);
        !           375:        old_bh->b_dirt = 1;
        !           376:        if (MSDOS_SB(old_dir->i_sb)->conversion == 'a') /* update binary info */
        !           377:                if (old_inode = iget(old_dir->i_dev,old_ino)) {
        !           378:                        msdos_read_inode(old_inode);
        !           379:                        iput(old_inode);
        !           380:                }
        !           381:        return 0;
        !           382: }
        !           383: 
        !           384: 
        !           385: static int rename_diff_dir(struct inode *old_dir,char *old_name,
        !           386:     struct inode *new_dir,char *new_name,struct buffer_head *old_bh,
        !           387:     struct msdos_dir_entry *old_de,int old_ino)
        !           388: {
        !           389:        struct buffer_head *new_bh,*free_bh,*dotdot_bh;
        !           390:        struct msdos_dir_entry *new_de,*free_de,*dotdot_de;
        !           391:        struct inode *old_inode,*new_inode,*free_inode,*dotdot_inode,*walk;
        !           392:        int new_ino,free_ino,dotdot_ino;
        !           393:        int error,exists,ino;
        !           394: 
        !           395:        if (old_dir->i_dev != new_dir->i_dev) return -EINVAL;
        !           396:        if (old_ino == new_dir->i_ino) return -EINVAL;
        !           397:        if (!(walk = iget(new_dir->i_dev,new_dir->i_ino))) return -EIO;
        !           398:        while (walk->i_ino != MSDOS_ROOT_INO) {
        !           399:                ino = msdos_parent_ino(walk,1);
        !           400:                iput(walk);
        !           401:                if (ino < 0) return ino;
        !           402:                if (ino == old_ino) return -EINVAL;
        !           403:                if (!(walk = iget(new_dir->i_dev,ino))) return -EIO;
        !           404:        }
        !           405:        iput(walk);
        !           406:        if ((error = msdos_scan(new_dir,NULL,&free_bh,&free_de,&free_ino)) < 0)
        !           407:            return error;
        !           408:        exists = msdos_scan(new_dir,new_name,&new_bh,&new_de,&new_ino)
        !           409:            >= 0;
        !           410:        if (!(old_inode = iget(old_dir->i_dev,old_ino))) {
        !           411:                brelse(free_bh);
        !           412:                if (exists) brelse(new_bh);
        !           413:                return -EIO;
        !           414:        }
        !           415:        if (*(unsigned char *) old_de->name == DELETED_FLAG) {
        !           416:                iput(old_inode);
        !           417:                brelse(free_bh);
        !           418:                if (exists) brelse(new_bh);
        !           419:                return -ENOENT;
        !           420:        }
        !           421:        new_inode = NULL; /* to make GCC happy */
        !           422:        if (exists) {
        !           423:                if (!(new_inode = iget(new_dir->i_dev,new_ino))) {
        !           424:                        iput(old_inode);
        !           425:                        brelse(new_bh);
        !           426:                        return -EIO;
        !           427:                }
        !           428:                if (S_ISDIR(new_inode->i_mode)) {
        !           429:                        iput(new_inode);
        !           430:                        iput(old_inode);
        !           431:                        brelse(new_bh);
        !           432:                        return -EPERM;
        !           433:                }
        !           434:                new_inode->i_nlink = 0;
        !           435:                new_inode->i_data[D_BUSY] = 1;
        !           436:                new_inode->i_dirt = 1;
        !           437:                new_de->name[0] = DELETED_FLAG;
        !           438:                new_bh->b_dirt = 1;
        !           439:        }
        !           440:        memcpy(free_de,old_de,sizeof(struct msdos_dir_entry));
        !           441:        memcpy(free_de->name,new_name,MSDOS_NAME);
        !           442:        if (!(free_inode = iget(new_dir->i_dev,free_ino))) {
        !           443:                free_de->name[0] = DELETED_FLAG;
        !           444: /*  Don't mark free_bh as dirty. Both states are supposed to be equivalent. */
        !           445:                brelse(free_bh);
        !           446:                if (exists) {
        !           447:                        iput(new_inode);
        !           448:                        brelse(new_bh);
        !           449:                }
        !           450:                return -EIO;
        !           451:        }
        !           452:        msdos_read_inode(free_inode);
        !           453:        old_inode->i_data[D_BUSY] = 1;
        !           454:        old_inode->i_dirt = 1;
        !           455:        old_de->name[0] = DELETED_FLAG;
        !           456:        old_bh->b_dirt = 1;
        !           457:        free_bh->b_dirt = 1;
        !           458:        if (!exists) iput(free_inode);
        !           459:        else {
        !           460:                new_inode->i_data[D_DEPEND] = (int) free_inode;
        !           461:                free_inode->i_data[D_OLD] = (int) new_inode;
        !           462:                /* free_inode is put when putting new_inode */
        !           463:                iput(new_inode);
        !           464:                brelse(new_bh);
        !           465:        }
        !           466:        if (S_ISDIR(old_inode->i_mode)) {
        !           467:                if ((error = msdos_scan(old_inode,MSDOS_DOTDOT,&dotdot_bh,
        !           468:                    &dotdot_de,&dotdot_ino)) < 0) goto rename_done;
        !           469:                if (!(dotdot_inode = iget(old_inode->i_dev,dotdot_ino))) {
        !           470:                        brelse(dotdot_bh);
        !           471:                        error = -EIO;
        !           472:                        goto rename_done;
        !           473:                }
        !           474:                dotdot_de->start = dotdot_inode->i_data[D_START] =
        !           475:                    new_dir->i_data[D_START];
        !           476:                dotdot_inode->i_dirt = 1;
        !           477:                dotdot_bh->b_dirt = 1;
        !           478:                iput(dotdot_inode);
        !           479:                brelse(dotdot_bh);
        !           480:        }
        !           481:        error = 0;
        !           482: rename_done:
        !           483:        brelse(free_bh);
        !           484:        iput(old_inode);
        !           485:        return error;
        !           486: }
        !           487: 
        !           488: 
        !           489: int msdos_rename(struct inode *old_dir,const char *old_name,int old_len,
        !           490:        struct inode *new_dir,const char *new_name,int new_len)
        !           491: {
        !           492:        char old_msdos_name[MSDOS_NAME],new_msdos_name[MSDOS_NAME];
        !           493:        struct buffer_head *old_bh;
        !           494:        struct msdos_dir_entry *old_de;
        !           495:        int old_ino,error;
        !           496: 
        !           497:        if ((error = msdos_format_name(MSDOS_SB(old_dir->i_sb)->name_check,
        !           498:            old_name,old_len,old_msdos_name)) < 0) goto rename_done;
        !           499:        if ((error = msdos_format_name(MSDOS_SB(new_dir->i_sb)->name_check,
        !           500:            new_name,new_len,new_msdos_name)) < 0) goto rename_done;
        !           501:        if ((error = msdos_scan(old_dir,old_msdos_name,&old_bh,&old_de,
        !           502:            &old_ino)) < 0) goto rename_done;
        !           503:        lock_creation();
        !           504:        if (old_dir == new_dir)
        !           505:                error = rename_same_dir(old_dir,old_msdos_name,new_dir,
        !           506:                    new_msdos_name,old_bh,old_de,old_ino);
        !           507:        else error = rename_diff_dir(old_dir,old_msdos_name,new_dir,
        !           508:                    new_msdos_name,old_bh,old_de,old_ino);
        !           509:        unlock_creation();
        !           510:        brelse(old_bh);
        !           511: rename_done:
        !           512:        iput(old_dir);
        !           513:        iput(new_dir);
        !           514:        return error;
        !           515: }

unix.superglobalmegacorp.com

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