/* * jfsread - read-only reader for AIX JFS (JFS1) filesystems, from a * logical volume device or a dd image, on AIX or Linux. * * usage: jfsread [-o offset] image command [args] * super superblock * ls PATH|#INO directory listing: inode mode nlink uid gid size mtime name * find [PATH] recursive listing (same columns, full paths) * cat PATH|#INO file contents to stdout * stat PATH|#INO one inode, all fields * -o offset byte offset of the filesystem within the image * * Layout (from /usr/include/jfs on AIX 4.3.3): superblock at 4096 (copy * at block 31), magic 0x43218765; 128-byte disk inodes, inode i at byte * i*128 of the .inodes file (inode 3, itself at block 32 + 3*128); disk * addresses are 32 bits: newbit:1 nfrags:3 addr:28, in fragments. * Files <= 32 KB use 8 direct addresses, <= 4 MB a single indirect page * of 1024 addresses, larger a double indirect root of 512 (vaddr,raddr). */ #define _LARGE_FILES 1 #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #define PAGE 4096 #define DILEN 128 #define ROOT_INO 2 static int fd; static off_t base; static unsigned long fragsize = PAGE; static unsigned long bigexp; /* big-file filesystems: 2^bigexp pages per block past 4 MB */ /* * Raw (character) devices on AIX only accept reads that start on a * 512-byte boundary and cover whole sectors, so read the enclosing * sectors and copy out the part wanted. */ static void rd(off_t off, void *buf, size_t n) { static unsigned char *tmp; static size_t tmplen; off_t a = (base + off) & ~(off_t)511; size_t skip = (size_t)(base + off - a); size_t len = (skip + n + 511) & ~(size_t)511; if (len > tmplen) { free(tmp); tmplen = len; if ((tmp = malloc(tmplen)) == NULL) { fprintf(stderr, "jfsread: out of memory\n"); exit(2); } } if (lseek(fd, a, SEEK_SET) == (off_t)-1 || read(fd, tmp, len) != (ssize_t)len) { fprintf(stderr, "jfsread: read of %lu bytes at %.0f failed\n", (unsigned long)n, (double)(base + off)); exit(2); } memcpy(buf, tmp + skip, n); } static unsigned long be32(unsigned char *p) { return ((unsigned long)p[0] << 24) | ((unsigned long)p[1] << 16) | ((unsigned long)p[2] << 8) | p[3]; } static unsigned be16(unsigned char *p) { return (p[0] << 8) | p[1]; } static off_t daddr(unsigned long v) /* disk address -> byte offset */ { return (off_t)(v & 0x0fffffffUL) * fragsize; } struct inode { unsigned long ino, gen, mode, nlink, uid, gid, size, nblocks; unsigned long mtime, atime, ctime; unsigned long addr[8], vind, rind; unsigned char raw[DILEN]; }; static unsigned long agsize, iagsize; /* fragments / inodes per allocation group */ static void decode(unsigned char *d, unsigned long ino, struct inode *ip) { int i; memcpy(ip->raw, d, DILEN); ip->ino = ino; ip->gen = be32(d); ip->mode = be32(d + 4); ip->nlink = be16(d + 8); ip->uid = be32(d + 12); ip->gid = be32(d + 16); ip->size = be32(d + 20); ip->nblocks = be32(d + 24); ip->mtime = be32(d + 28); ip->atime = be32(d + 36); ip->ctime = be32(d + 44); for (i = 0; i < 8; i++) ip->addr[i] = be32(d + 80 + 4 * i); ip->vind = be32(d + 112); ip->rind = be32(d + 116); } /* byte offset of page pno of a file, or -1 for a hole */ static off_t pageaddr(struct inode *ip, unsigned long pno) { unsigned char b[8]; unsigned long v; if (ip->size <= 8UL * PAGE) { if (pno >= 8) return -1; v = ip->addr[pno]; } else if (ip->size <= 1024UL * PAGE) { rd(daddr(ip->rind) + 4 * pno, b, 4); v = be32(b); } else { /* * Double indirect. Pages below 1024 are covered by root entry 0; * past that, on a big-file fs, each address covers 2^bigexp * contiguous pages (ino.h: SGLOFF, DBLOFF). */ unsigned long r, slot, within = 0; if (pno < 1024) { r = 0; slot = pno; } else { r = ((pno - 1024) >> (bigexp + 10)) + 1; slot = ((pno - 1024) >> bigexp) & 1023; within = (pno - 1024) & ((1UL << bigexp) - 1); } rd(daddr(ip->rind) + 8 * r, b, 8); /* root: (vaddr, raddr) */ v = be32(b + 4); if ((v & 0x0fffffffUL) == 0) return -1; rd(daddr(v) + 4 * slot, b, 4); v = be32(b); if ((v & 0x0fffffffUL) == 0) return -1; return daddr(v) + (off_t)within * PAGE; } if ((v & 0x0fffffffUL) == 0) return -1; return daddr(v); } /* read len bytes at byte offset off of a file */ static void fread_at(struct inode *ip, unsigned long off, unsigned char *buf, unsigned long len) { unsigned long n; off_t a; while (len > 0) { n = PAGE - off % PAGE; if (n > len) n = len; a = pageaddr(ip, off / PAGE); if (a < 0) memset(buf, 0, n); else rd(a + off % PAGE, buf, n); off += n; buf += n; len -= n; } } /* * Inodes are placed by allocation-group geometry: group 0's inodes start * at block 32 (after the superblocks), group n's at the group's first * fragment. (The .inodes inode, #3, has size 0 and no addresses on disk.) */ static void iget(unsigned long ino, struct inode *ip) { unsigned char d[DILEN]; unsigned long ag = ino / iagsize, idx = ino % iagsize; off_t start = ag == 0 ? (off_t)32 * PAGE : (off_t)ag * agsize * fragsize; rd(start + (off_t)idx * DILEN, d, DILEN); decode(d, ino, ip); } static char * modestr(unsigned long m) { static char s[11]; static char t[] = "?pc?d?b?-?l?s???"; int i; s[0] = t[(m >> 12) & 15]; for (i = 0; i < 9; i++) s[1 + i] = (m & (0400 >> i)) ? "rwxrwxrwx"[i] : '-'; if (m & 04000) s[3] = (m & 0100) ? 's' : 'S'; if (m & 02000) s[6] = (m & 010) ? 's' : 'S'; if (m & 01000) s[9] = (m & 01) ? 't' : 'T'; s[10] = '\0'; return s; } static void line(struct inode *ip, char *name) { printf("%lu %s %lu %lu %lu %lu %lu %s\n", ip->ino, modestr(ip->mode), ip->nlink, ip->uid, ip->gid, ip->size, ip->mtime, name); } typedef void (*dirfn)(unsigned long ino, char *name, void *arg); static void readdir_(struct inode *dp, dirfn fn, void *arg) { unsigned char *buf; unsigned long off = 0, ino, reclen, namlen; char name[256]; if ((buf = malloc(dp->size + 1)) == NULL) return; fread_at(dp, 0, buf, dp->size); while (off + 8 <= dp->size) { ino = be32(buf + off); reclen = be16(buf + off + 4); namlen = be16(buf + off + 6); if (reclen < 8 || off + reclen > dp->size) break; if (ino != 0 && namlen > 0 && namlen < 256 && 8 + namlen <= reclen) { memcpy(name, buf + off + 8, namlen); name[namlen] = '\0'; fn(ino, name, arg); } off += reclen; } free(buf); } struct lookup { char *want; unsigned long found; }; static void lookfn(unsigned long ino, char *name, void *arg) { struct lookup *l = arg; if (strcmp(name, l->want) == 0) l->found = ino; } static unsigned long namei(char *path) { struct inode d; struct lookup l; char *p, *q, buf[1024]; unsigned long ino = ROOT_INO; if (path[0] == '#') return strtoul(path + 1, NULL, 10); strncpy(buf, path, sizeof buf - 1); buf[sizeof buf - 1] = '\0'; for (p = strtok(buf, "/"); p; p = strtok(NULL, "/")) { iget(ino, &d); if ((d.mode & 0170000) != 0040000) return 0; l.want = p; l.found = 0; readdir_(&d, lookfn, &l); if ((ino = l.found) == 0) return 0; } (void)q; return ino; } static void lsfn(unsigned long ino, char *name, void *arg) { struct inode ip; (void)arg; iget(ino, &ip); line(&ip, name); } static void findfn(unsigned long ino, char *name, void *arg) { struct inode ip; char path[2048]; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) return; sprintf(path, "%.1000s/%.1000s", (char *)arg, name); iget(ino, &ip); line(&ip, path); if ((ip.mode & 0170000) == 0040000) readdir_(&ip, findfn, path); } static void super(void) { unsigned char s[PAGE]; time_t t; char *names[] = {"s_time (created)", "s_mount_ts", "s_dirty_ts", "s_unmnt_ts"}; int offs[] = {44, 76, 80, 84}, i; rd(PAGE, s, PAGE); printf("magic %02x%02x%02x%02x\n", s[0], s[1], s[2], s[3]); printf("flag %02x %02x %02x %02x\n", s[4], s[5], s[6], s[7]); printf("agsize %lu fragments\n", be32(s + 8)); printf("logserial %lu\n", be32(s + 12)); printf("fsize %lu sectors (%.1f MB)\n", be32(s + 16), be32(s + 16) / 2048.0); printf("bsize %u\n", be16(s + 20)); printf("fname %.6s\n", (char *)s + 24); printf("fpack %.6s\n", (char *)s + 30); printf("logdev 0x%08lx\n", be32(s + 36)); printf("fmod %d\nronly %d\n", s[40], s[41]); printf("version %lu (0 = v3, 1 = v3p, 2 = big file)\n", be32(s + 48)); printf("fragsize %lu\n", be32(s + 52)); printf("iagsize %lu\n", be32(s + 56)); printf("compress %lu\n", be32(s + 60)); printf("bigexp %lu\n", be32(s + 64)); printf("mntflag 0x%lx\n", be32(s + 68)); printf("info 0x%lx\n", be32(s + 72)); for (i = 0; i < 4; i++) { t = (time_t)be32(s + offs[i]); printf("%-17s %lu %s", names[i], (unsigned long)t, t ? asctime(gmtime(&t)) : "-\n"); } } int main(int argc, char **argv) { unsigned char s[PAGE]; struct inode ip; unsigned long ino, off, n; char *img, *cmd; int i = 1; if (argc > 2 && strcmp(argv[1], "-o") == 0) { base = (off_t)strtod(argv[2], NULL); i = 3; } if (argc - i < 2) { fprintf(stderr, "usage: jfsread [-o offset] image super|ls|find|cat|stat [path|#ino]\n"); return 1; } img = argv[i]; cmd = argv[i + 1]; if ((fd = open(img, O_RDONLY)) < 0) { perror(img); return 1; } rd(PAGE, s, PAGE); if (memcmp(s, "\103\041\207\145", 4) != 0 && memcmp(s, "\145\207\041\103", 4) != 0) { fprintf(stderr, "%s: no JFS superblock at offset %.0f\n", img, (double)base + PAGE); return 1; } if (be32(s + 48) >= 1 && be32(s + 52) != 0) fragsize = be32(s + 52); if (be32(s + 48) == 2) bigexp = be32(s + 64); agsize = be32(s + 8); iagsize = be32(s + 48) >= 1 ? be32(s + 56) : agsize * fragsize / PAGE; if (strcmp(cmd, "super") == 0) { super(); return 0; } ino = namei(argc - i > 2 ? argv[i + 2] : "/"); if (ino == 0) { fprintf(stderr, "jfsread: %s: not found\n", argv[i + 2]); return 1; } iget(ino, &ip); if (strcmp(cmd, "ls") == 0) { readdir_(&ip, lsfn, NULL); } else if (strcmp(cmd, "find") == 0) { readdir_(&ip, findfn, argc - i > 2 && strcmp(argv[i + 2], "/") ? argv[i + 2] : ""); } else if (strcmp(cmd, "stat") == 0) { line(&ip, argc - i > 2 ? argv[i + 2] : "/"); printf("gen %lu nblocks %lu atime %lu ctime %lu addr", ip.gen, ip.nblocks, ip.atime, ip.ctime); for (n = 0; n < 8; n++) printf(" %08lx", ip.addr[n]); printf(" ind %08lx/%08lx\n", ip.vind, ip.rind); } else if (strcmp(cmd, "cat") == 0) { unsigned char buf[PAGE]; for (off = 0; off < ip.size; off += n) { n = ip.size - off < PAGE ? ip.size - off : PAGE; fread_at(&ip, off, buf, n); fwrite(buf, 1, n, stdout); } } else { fprintf(stderr, "jfsread: unknown command %s\n", cmd); return 1; } return 0; }