# /* fsdb - file system debugger */ /* by Marc Pucci - PY x4441 */ #include "stat.h" #include "stdio.h" #define NBUF 3 #define MODE 0 #define LINK 2 #define UID 3 #define GID 4 #define S0 5 #define S1 6 #define A0 8 #define MIN 8 #define MAJ 9 #define AT 24 #define MT 28 #define NUMB 10 #define INODE 11 #define BLOCK 12 #define DIR 13 #define NM 14 struct { int hiword; int loword; }; struct { char lobyte; char hibyte; }; struct { long dblwrd; }; struct buf { struct buf *fwd; struct buf *back; char *blkaddr; int valid; unsigned blkno; } buf[NBUF], bhdr; char buffers[NBUF][512]; struct dir { int inumb; char nm[14]; } *dirp; struct inode { int md; char ln; char uid; char gid; char s0; int s1; int a[8]; long at; long mt; } *ip; long addr, value, temp, oldaddr, oldvalue, erraddr; long curi 02000; int psize 2; int fd, c_count, i, j, k, oldpsize, error, type; int count, pid, rpid, retcode, mode, cflag, prflag; unsigned isize, fsize; char *p; int override; /* main - continuously looping input scanner. Lines are scanned * a character at a time and are processed as soon as * sufficient information has been collected. When an error * is detected, the remainder of the line is flushed. */ main(argc,argv) int argc; char **argv; { register char *cptr; register char c; register int *iptr; extern long get(); extern long getnumb(); extern err(); register struct buf *bp; unsigned block; int offset; setbuf(stdin,NULL); if(argc!=2 && argc!=3) { printf("usage: fsdb /dev/rp??\n"); exit(1); } if((fd = open(argv[1],2)) < 0) { printf("cannot open %s\n",argv[1]); exit(1); } bhdr.fwd = bhdr.back = &bhdr; for(i=0; i<NBUF; i++) { bp = &buf[i]; bp->blkaddr = buffers[i]; bp->valid = 0; insert(bp); } override++; reload(); if(argc == 3) printf("error checking off\n"); else override = 0; signal(2,err); setexit(); for(;;) { if(error) { if(c != '\n') while (getc(stdin) != '\n'); c_count = 0; prflag = 0; cflag = 0; error = 0; addr = erraddr; printf("?\n"); /* type = -1; allows "d31 + +" to work */ } c_count++; switch(c = getc(stdin)) { case '\n': /* command end */ erraddr = addr; if(c_count == 1) addr =+ psize; c_count = 0; if(prflag) { prflag = 0; continue; } temp = get(psize); /* if an error has been flagged, it is probably * due to allignment. This will have set psize * to 1 hence the next get should work. */ if(error) temp = get(psize); switch(psize) { case 1: cptr = ".B"; break; case 2: cptr = ""; break; case 4: cptr = ".D"; break; case 14: case 16: if(bcomp(addr,erraddr)) continue; fprnt('d', 1); prflag = 0; continue; case 32: fprnt('i',1); curi = addr; prflag = 0; continue; } printf("%6O%s: %6O (%D)\n",addr,cptr,temp,temp); continue; default: /* catch absolute addresses, b and i#'s */ if(c<='9' && c>='0') { ungetc(c,stdin); addr = getnumb(); psize = 2; value = addr; type = NUMB; continue; } if(feof(stdin)) exit(0); error++; continue; case 'i': /* i# to inode conversion */ if(c_count == 1) { addr = curi; value = get(32); type = INODE; continue; } if(type==NUMB)value = addr; addr = ((value - 1) << 5) + 1024; if(icheck(addr)) continue; curi = addr; value = get(32); type = INODE; continue; case 'b': /* block conversion */ if(type == NUMB)value = addr; addr = value << 9; value = get(2); type = BLOCK; continue; case 'd': /* directory offsets */ value = getnumb(); if(error||(value > 31)) { error++; continue; } if(value != 0) if(dircheck()) continue; addr = (addr & ~0777) + (value << 4); value = get(16); /* i-number */ type = DIR; continue; case '\t': case ' ': case '.': continue; case '+': /* address addition */ c = getc(stdin); ungetc(c,stdin); if(c > '9' || c < '0') temp = psize; else temp = getnumb() * psize; if(error) continue; if(psize == 14 || psize == 16) if(bcomp(addr,addr+temp)) { c = '+'; continue; } addr =+ temp; value = get(psize); continue; case '-': /* address subtraction */ c = getc(stdin); ungetc(c,stdin); if(c > '9' || c < '0') temp = psize; else temp = getnumb() * psize; if(error) continue; if(psize == 14 || psize == 16) if(bcomp(addr,addr-temp)) { c = '-'; continue; } addr =- temp; value = get(psize); continue; case '*': temp = getnumb(); if(error) continue; addr =* temp; value = get(psize); continue; case '/': temp = getnumb(); if(error) continue; addr =/ temp; value = get(psize); continue; case 'q': /* quit */ if(c_count != 1 || (c = getc(stdin)) != '\n') { error++; continue; } exit(0); case '>': /* save current address */ oldaddr = addr; oldvalue = value; oldpsize = psize; continue; case '<': /* restore saved address */ addr = oldaddr; value = oldvalue; psize = oldpsize; continue; case 'a': /* access time */ if((c = getc(stdin)) == 't') { addr = curi + AT; type = AT; value = get(4); continue; } ungetc(c,stdin); /* data block addresses */ value = getnumb(); if(error||(value > 7)) { error++; continue; } addr = curi + A0 + (value << 1); value = get(2); type = A0; continue; case 'm': /* mt, md, maj, min */ addr = curi; mode = get(2); if(error) continue; switch(c = getc(stdin)) { case 't': /* modification time */ addr =+ MT; type = MT; value = get(4); continue; case 'd': /* mode */ addr =+ MODE; type = MODE; value = get(2); continue; case 'a': /* major device number */ if((c = getc(stdin)) != 'j') { error++; continue; } if(devcheck(mode)) continue; addr =+ MAJ; value = get(1); type = MAJ; continue; case 'i': /* minor device number */ if((c = getc(stdin)) != 'n') { error++; continue; } if(devcheck(mode)) continue; addr =+ MIN; value = get(1); type = MIN; continue; } error++; continue; case 's': /* file size */ switch(c = getc(stdin)) { case '0': /* high byte of file size */ addr = curi + S0; value = get(1); type = S0; continue; case '1': /* low word of file size */ addr = curi + S1; value = get(2); type = S1; continue; default: error++; continue; } case 'l': /* link count */ if((c = getc(stdin)) != 'n') { error++; continue; } addr = curi + LINK; value = get(1); type = LINK; continue; case 'g': /* group id */ if((c=getc(stdin))!= 'i' || (c=getc(stdin)) != 'd') { error++; continue; } addr = curi + GID; value = get(1); type = GID; continue; case 'u': /* user id */ if((c=getc(stdin))!= 'i' || (c=getc(stdin)) != 'd') { error++; continue; } addr = curi + UID; value = get(1); type = UID; continue; case 'n': /* directory name */ if((c = getc(stdin)) != 'm') { error++; continue; } if(dircheck()) continue; type = NM; psize = 14; addr = (addr & ~017) + 2; continue; case '=': /* assignment operation */ switch(c = getc(stdin)) { case '"': /* character string */ puta(); continue; case '+': /* =+ operator */ temp = getnumb(); value = get(psize); if(!error) put(value+temp,psize); continue; case '-': /* =- operator */ temp = getnumb(); value = get(psize); if(!error) put(value-temp,psize); continue; default: /* nm and regular assignment */ ungetc(c,stdin); if((type == NM) && (c > '9' || c < '0')) { puta(); continue; } value = getnumb(); if(!error) put(value,psize); continue; } case '!': /* shell command */ if(c_count != 1) { error++; continue; } if((pid = fork()) == 0) { execl("/bin/sh", "sh", "-t", 0); error++; continue; } while((rpid = wait(&retcode)) != pid && rpid != -1); printf("!\n"); c_count = 0; continue; case 'F': /* buffer status */ for(bp=bhdr.fwd; bp!= &bhdr; bp=bp->fwd) printf("%6u %d\n",bp->blkno,bp->valid); continue; case 'f': /* file print facility */ if((c=getc(stdin)) >= '0' && c <= '9') { ungetc(c,stdin); temp = getnumb(); if (error) continue; c = getc(stdin); } else temp = 0; count = 0; addr = curi; mode = get(2); if(!override) { if((mode & IALLOC)==0) printf("warning: inode not allocated\n"); } if(mode & IFCHR) { printf("special device\n"); error++; continue; } if(mode & ILARG) { addr = curi + ((temp >> 8) << 1) + A0; addr = get(2) << 9; if(nullblk()) continue; addr = addr + ((temp & 0377) << 1); addr = get(2) << 9; if(nullblk()) continue; fprnt(c,0); continue; } if(temp > 7) { printf("small file\n"); error++; continue; } addr = curi + (temp << 1) + A0; addr = get(2) << 9; if(nullblk()) continue; fprnt(c,0); continue; case 'O': /* override flip flop */ if(override = !override) printf("error checking off\n"); else { printf("error checking on\n"); reload(); } prflag++; continue; case 'B': /* byte offsets */ psize = 1; continue; case 'W': /* word offsets */ psize = 2; addr =& ~01; continue; case 'D': /* double word offsets */ psize = 4; addr =& ~03; continue; case ',': /* general print facilities */ case 'p': if(( c = getc(stdin)) >= '0' && c <= '9') { ungetc(c,stdin); count = getnumb(); if(error) continue; c = getc(stdin); } else count = 1; fprnt(c,count); } } } /* getnumb - read a number from the input stream. A leading * zero signifies octal interpretation. If the first character * is not numeric this is an error, otherwise continue * until the first non-numeric. */ long getnumb() { extern int error; long number, base; register char c; if(((c = getc(stdin)) < '0')||(c > '9')) { error++; ungetc(c,stdin); return(-1); } if(c == '0') base = 8; else base = 10; number = c - '0'; while(((c = getc(stdin)) >= '0' )&&( c <= '9')) { if((base == 8) && ((c =='8')||(c == '9'))) { error++; return(-1); } number = number * base + c - '0'; } ungetc(c,stdin); return(number); } /* get - read a byte, word or double word from the file system. * The entire block containing the desired item is read * and the appropriate data is extracted and returned. * Inode and directory size requests result in word * fetches. Directory names (psize == 14) result in byte * fetches. */ long get(lngth) int lngth; { long vtemp; char *bptr; unsigned block; int offset; psize = lngth; if(allign(psize)) return(-1); block = addr >> 9; if((bptr = getblk(block)) == 0) return(-1); vtemp = 0; offset = addr & 0777; bptr =+ offset; if(offset + lngth > 512) { error++; return(-1); } switch(psize) { case 4: return(bptr->dblwrd); case 32: case 16: case 2: vtemp.loword = bptr->hiword; return(vtemp); case 14: case 1: vtemp.loword.lobyte = *bptr; return(vtemp); } error++; return(-1); } /* icheck - check if the current address is within the I-list. * The I-list extends for isize blocks beyond the * super block, i.e., from block 2 to block isize + 1. */ icheck(address) long address; { unsigned blk; if(override) return(0); blk = address >> 9; if((blk >= 2) && (blk < isize + 2)) return(0); printf("inode out of range\n"); error++; return(1); } /* putf - print a byte as an ascii character if possible. * The exceptions are tabs, newlines, backslashes * and nulls which are printed as the standard c * language escapes. Characters which are not * recognized are printed as \?. */ putf(c) register char c; { if(c<=037 || c>=0177 || c=='\\') { putc('\\',stdout); switch(c) { case '\\': putc('\\',stdout); break; case '\t': putc('t',stdout); break; case '\n': putc('n',stdout); break; case '\0': putc('0',stdout); break; default: putc('?',stdout); } } else { putc(' ',stdout); putc(c,stdout); } putc(' ',stdout); } /* put - write an item into the buffer for the current address * block. The value is checked to make sure that it will * fit in the size given without truncation. If successful, * the entire block is written back to the file system. */ put(item,lngth) long item; int lngth; { register char *bptr, *sbptr; int offset; unsigned block; psize = lngth; if(allign(psize)) return(-1); block = addr >> 9; if((sbptr = getblk(block)) == 0) return; offset = addr & 0777; if(offset + lngth > 512) { error++; printf("block overflow\n"); return; } bptr = sbptr + offset; switch(psize) { case 4: bptr->dblwrd = item; goto rite; case 32: case 16: case 2: if(item & ~0177777L) break; bptr->hiword = item.loword; goto rite; case 14: case 1: if(item & ~0377) break; *bptr = item.loword.lobyte; rite: if(seek(fd,block,3)||write(fd,sbptr,512)!=512) error++; return; default: error++; return; } printf("truncation error\n"); error++; } /* getblk - check if the desired block is in the file system. * Search the incore buffers to see if the block is already * available. If successful, unlink the buffer control block * from its position in the buffer list and re-insert it at * the head of the list. If failure, use the last buffer * in the list for the desired block. Again, this control * block is placed at the head of the list. This process * will leave commonly requested blocks in the in-core buffers. * Finally, a pointer to the buffer is returned. */ getblk(block) unsigned block; { register struct buf *bp; if(!override) if(block >= fsize) { printf("block out of range\n"); error++; return(0); } for(bp=bhdr.fwd; bp!= &bhdr; bp=bp->fwd) if(bp->blkno==block && bp->valid) goto xit; bp = bhdr.back; bp->blkno = block; bp->valid = 0; if(seek(fd,block,3)||read(fd,bp->blkaddr,512)!=512) { error++; return(0); } bp->valid++; xit: bp->back->fwd = bp->fwd; bp->fwd->back = bp->back; insert(bp); return(bp->blkaddr); } /* insert - place the designated buffer control block * at the head of the linked list of buffers. */ insert(bp) register struct buf *bp; { bp->back = &bhdr; bp->fwd = bhdr.fwd; bhdr.fwd->back = bp; bhdr.fwd = bp; } /* allign - before a get or put operation check the * current address for a boundary corresponding to the * size of the object. */ allign(ment) int ment; { switch(ment) { case 4: if(addr & 03L) break; return(0); case 14: if((addr & 017) != 02) break; return(0); case 16: case 32: case 2: if(addr & 01L) break; case 1: return(0); } error++; psize = 1; printf("allignment\n"); return(1); } /* err - called on interrupts. Set the current address * back to the last address stored in erraddr. Reset all * appropriate flags. If the prflag is set, the interrupt * occured while transferring characters to a buffer. * These are "erased" by invalidating the buffer, causing * the entire block to be re-read upon the next reference. * A reset call is made to return to the main loop; */ err() { signal(2,err); addr = erraddr; error = 0; c_count = 0; if(cflag) { bhdr.fwd->valid = 0; cflag = 0; } prflag = 0; printf("\n?\n"); fseek(stdin, 0L, 2); reset(); } /* devcheck - check that the given mode represents a * special device. The IFCHR bit is on for both * character and block devices. */ devcheck(md) register int md; { if(override) return(0); if(md & IFCHR) return(0); printf("not char or block device\n"); error++; return(1); } /* nullblk - return error if address is zero. This is done * to prevent block 0 from being used as an indirect block * for a large file or as a data block for a small file. */ nullblk() { if(addr != 0) return(0); printf("non existent block\n"); error++; return(1); } /* dircheck - check if the current address can be in a directory. * This means it is not in the I-list, block 0 or the super block. */ dircheck() { unsigned block; if(override) return (0); if((block = (addr >> 9)) > isize) return(0); error++; printf("block in I-list\n"); return(1); } /* puta - put ascii characters into a buffer. The string * terminates with a quote or newline. The leading quote, * which is optional for directory names, was stripped off * by the assignment case in the main loop. If the type * indicates a directory name, the entry is null padded to * 14 bytes. If more than 14 characters have been given * with this type or, in any case, if a block overflow * occurs, the current block is made invalid. See the * description for err. */ puta() { register char *bptr, c; register offset; char *sbptr; unsigned block; if((sbptr = getblk(block = addr >> 9)) == 0) return; cflag++; offset = addr & 0777; bptr = sbptr + offset; while((c = getc(stdin)) != '"') { if(offset++ == 512) { bhdr.fwd->valid = 0; error++; cflag = 0; printf("block overflow\n"); return; } if(c == '\n') { ungetc(c,stdin); break; } if(c == '\\') { switch(c = getc(stdin)) { case 't': *bptr++ = '\t'; break; case 'n': *bptr++ = '\n'; break; case '0': *bptr++ = '\0'; break; default: *bptr++ = c; break; } } else *bptr++ = c; } cflag = 0; if(type == NM) { c = offset - (addr & 0777); if(c > 14) { bhdr.fwd->valid = 0; error++; cflag = 0; printf("name too long\n"); return; } while(c++ < 14) *bptr++ = '\0'; } if(seek(fd,block,3) || write(fd,sbptr,512) != 512) error++; } /* fprnt - print data as characters, octal or decimal words, octal * bytes, directories or inodes A total of "count" entries * are printed. A zero count will print all entries to the * end of the current block. If the printing would cross a * block boundary, the attempt is aborted and an error returned. * This is done since logically sequential blocks are generally * not physically sequential. The error address is set * after each entry is printed. Upon completion, the current * address is set to that of the last entry printed. */ fprnt(style,count) register char style; register int count; { int offset; unsigned block; char *cptr; int *iptr; prflag++; block = addr >> 9; offset = addr & 0777; if((cptr = iptr = ip = dirp = getblk(block)) == 0) return; erraddr = addr; switch (style) { case 'c': /* print as characters */ case 'b': /* or octal bytes */ if(count == 0) count = 512 - offset; if(offset + count > 512) break; psize = 1; cptr =+ offset; for(i=0; count--; i++) { if(i % 16 == 0) printf("\n%6O: ",addr); if(style == 'c') putf(*cptr++); else printf("%4o",*cptr++ & 0377); erraddr = addr; addr++; } addr--; putc('\n',stdout); return; case 'd': /* print as directories */ if(dircheck()) return; addr =& ~017; offset =>> 4; if(count == 0) count = 32 - offset; if(count + offset > 32) break; type = DIR; psize = 16; for(dirp =+ offset; count--; dirp++) { printf("d%d: %4d ",offset++,dirp->inumb); cptr = &(dirp->nm); for(j=0; j<14; j++) { if(*cptr == '\0') break; putf(*cptr++); } putc('\n',stdout); erraddr = addr; addr =+ 16; } addr = erraddr; return; case 'o': /* print as octal words */ case 'e': /* print as decimal words */ addr =& ~01; offset =>> 1; if(count == 0) count = 256 - offset; if(offset + count > 256) break; psize = 2; iptr =+ offset; for(i=0; count--; i++) { if(i % 8 == 0) printf("\n%6O:",addr); if(style == 'o')printf(" %6o",*iptr++); else printf(" %6d",*iptr++); erraddr = addr; addr =+ 2; } addr = erraddr; putc('\n',stdout); return; case 'i': /* print as inodes */ if(icheck(addr)) return; addr =& ~037; offset =>> 5; if(count == 0) count = 16 - offset; if(count + offset > 16) break; type = INODE; psize = 32; ip =+ offset; temp = (addr - 1024) / 32 + 1; for(i=0; count--; ip++) { printf("i#:%5D md: ",temp++); p = " lugtrwxrwxrwx"; mode = ip->md; if(mode & IALLOC) putc('a',stdout); else putc('-',stdout); switch(mode & IFMT) { case IFDIR: putc('d',stdout); break; case IFCHR: putc('c',stdout); break; case IFBLK: putc('b',stdout); break; default: putc('-',stdout); break; } for(mode =<< 3; *++p; mode =<< 1) { if(mode & IALLOC) putc(*p,stdout); else putc('-',stdout); } printf(" ln:%4d uid:%4d gid:%4d", ip->ln&0377,ip->uid&0377,ip->gid&0377); printf(" s0:%4d s1:%6u\n",ip->s0&0377,ip->s1); if(ip->md & IFCHR) printf("maj:%3o min:%3o ",ip->a[0].hibyte, ip->a[0].lobyte); else { printf("a0:%5u a1:%5u a2:%5u a3:%5u ", ip->a[0],ip->a[1],ip->a[2],ip->a[3]); printf("a4:%5u a5:%5u a6:%5u a7:%5u\n", ip->a[4],ip->a[5],ip->a[6],ip->a[7]); } p = ctime(&ip->at); p[24] = '\0'; printf("at: %s ",p); printf("mt: %s",ctime(&ip->mt)); if(count) putc('\n',stdout); curi = erraddr = addr; addr =+ 32; } addr = erraddr; return; default: error++; printf("no such print option\n"); return; } error++; printf("block overflow\n"); } /* reload - read new values for isize and fsize. These are * the basis for most of the error checking procedures. */ reload() { long saddr; int spsize; saddr = addr; spsize = psize; addr = 01000; isize = get(2); addr = 01002; fsize = get(2); addr = saddr; psize = spsize; if(error) printf("cannot read superblock\n"); } /* bcomp - compare the block numbers of two long addresses. * Used to check for block over/under flows when stepping through * a file system. */ bcomp(addr1,addr2) long addr1; long addr2; { if(override) return(0); if((addr1 & ~0777L) == (addr2 & ~0777L)) return(0); error++; printf("block overflow\n"); return(1); }