#include #include #include #include #include #include #include <9p.h> /* little endian */ #define SHORT(p) (((uchar*)(p))[0] | (((uchar*)(p))[1] << 8)) #define LONG(p) ((ulong)SHORT(p) |(((ulong)SHORT((p)+2)) << 16)) typedef struct Ofile Ofile; typedef struct Odir Odir; enum { /* special block map entries */ Bspecial = 0xFFFFFFFD, Bendchain = 0xFFFFFFFE, Bunused = 0xFFFFFFFF, Blocksize = 0x200, Odirsize = 0x80, /* Odir types */ Tstorage = 1, Tstream = 2, Troot = 5, }; /* * the file consists of chains of blocks of size 0x200. * to find what block follows block n, you look at * blockmap[n]. that block follows it unless it is Bspecial * or Bendchain. * * it's like the MS-DOS file system allocation tables. */ struct Ofile { Biobuf *b; ulong nblock; ulong *blockmap; ulong rootblock; ulong smapblock; ulong *smallmap; }; /* Odir headers are found in directory listings in the Olefile */ /* prev and next form a binary tree of directory entries */ struct Odir { Ofile *f; Rune name[32+1]; uchar type; uchar isroot; ulong left; ulong right; ulong dir; ulong start; ulong size; }; void* emalloc(ulong sz) { void *v; v = malloc(sz); assert(v != nil); return v; } int convM2OD(Odir *f, void *buf, int nbuf) { int i; char *p; int len; if(nbuf < Odirsize) return -1; /* * the short at 0x40 is the length of the name. * when zero, it means there is no Odir here. */ p = buf; len = SHORT(p+0x40); if(len == 0) return 0; if(len > 32) /* shouldn't happen */ len = 32; for(i=0; iname[i] = SHORT(p+i*2); f->name[len] = 0; f->type = p[0x42]; f->left = LONG(p+0x44); f->right = LONG(p+0x48); f->dir = LONG(p+0x4C); f->start = LONG(p+0x74); f->size = LONG(p+0x78); /* BUG: grab time in ms format from here */ return 1; } int oreadblock(Ofile *f, int block, ulong off, char *buf, int nbuf) { if(block < 0 || block >= f->nblock) { werrstr("attempt to read %x/%lux\n", block, f->nblock); return -1; } if(off >= Blocksize) return 0; if(off+nbuf > Blocksize) nbuf = Blocksize-off; /* blocks start numbering at -1 [sic] */ off += (block+1)*Blocksize; if(Bseek(f->b, off, 0) != off) return -1; return Bread(f->b, buf, nbuf); } int chainlen(Ofile *f, ulong start) { int i; for(i=0; start < 0xFFFF0000; i++) start = f->blockmap[start]; return i; } /* * read nbuf bytes starting at offset off from the * chain whose first block is block. the chain is linked * together via the blockmap as described above, * like the MS-DOS file allocation tables. */ int oreadchain(Ofile *f, ulong block, int off, char *buf, int nbuf) { int i; int offblock; offblock = off/Blocksize; for(i=0; iblockmap[block]; return oreadblock(f, block, off%Blocksize, buf, nbuf); } int oreadfile(Odir *d, int off, char *buf, int nbuf) { /* * if d->size < 0x1000 then d->start refers * to a small depot block, else a big one. * if this is the root entry, it's a big one * no matter what. */ if(off >= d->size) return 0; if(off+nbuf > d->size) nbuf = d->size-off; if(d->size >= 0x1000 || memcmp(d->name, L"Root Entry", 11*sizeof(Rune)) == 0) return oreadchain(d->f, d->start, off, buf, nbuf); else { /* small block */ off += d->start*64; return oreadchain(d->f, d->f->smapblock, off, buf, nbuf); } } int oreaddir(Ofile *f, int entry, Odir *d) { char buf[Odirsize]; if(oreadchain(f, f->rootblock, entry*Odirsize, buf, Odirsize) != Odirsize) return -1; d->f = f; return convM2OD(d, buf, Odirsize); } void dumpdir(Ofile *f, ulong dnum) { Odir d; if(oreaddir(f, dnum, &d) != 1) { fprint(2, "dumpdir %lux failed\n", dnum); return; } fprint(2, "%.8lux type %d size %lud l %.8lux r %.8lux d %.8lux (%S)\n", dnum, d.type, d.size, d.left, d.right, d.dir, d.name); if(d.left != (ulong)-1) dumpdir(f, d.left); if(d.right != (ulong)-1) dumpdir(f, d.right); if(d.dir != (ulong)-1) dumpdir(f, d.dir); } Ofile* oleopen(char *fn) { int i, j, k, block; int ndepot; ulong u; Odir rootdir; ulong extrablock; uchar buf[Blocksize]; Ofile *f; Biobuf *b; static char magic[] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; b = Bopen(fn, OREAD); if(b == nil) return nil; /* the first bytes are magic */ if(Bread(b, buf, sizeof magic) != sizeof magic || memcmp(buf, magic, sizeof magic) != 0) { werrstr("bad magic: not OLE file"); Bterm(b); return nil; } f = emalloc(sizeof *f); f->b = b; /* * the header contains a list of depots, which are * block maps. we assimilate them into one large map, * kept in main memory. */ Bseek(b, 0, 0); if(Bread(b, buf, Blocksize) != Blocksize) { Bterm(b); free(f); return nil; } ndepot = LONG(buf+0x2C); f->nblock = ndepot*(Blocksize/4); // fprint(2, "ndepot = %d f->nblock = %lud\n", ndepot, f->nblock); f->rootblock = LONG(buf+0x30); f->smapblock = LONG(buf+0x3C); f->blockmap = emalloc(sizeof(f->blockmap[0])*f->nblock); extrablock = LONG(buf+0x44); u = 0; /* the big block map fills to the end of the first 512-byte block */ for(i=0; inblock = ndepot*(Blocksize/4); break; } if(Bseek(b, (block+1)*Blocksize, 0) != (block+1)*Blocksize) { print("Xbseek %d fail\n", (block+1)*Blocksize); goto Die; } for(j=0; jblockmap[u++] = LONG(buf); } } /* * if the first block can't hold it, it continues in the block at LONG(hdr+0x44). * if that in turn is not big enough, there's a next block number at the end of * each block. */ while(i < ndepot) { for(k=0; k<(0x200-4)/4 && inblock = ndepot*(Blocksize/4); goto Break2; } if(Bseek(b, (block+1)*Blocksize, 0) != (block+1)*Blocksize) { print("Xbseek %d fail\n", (block+1)*Blocksize); goto Die; } for(j=0; jblockmap[u++] = LONG(buf); } } if(Bseek(b, 0x200+extrablock*Blocksize+Blocksize-4, 0) != 0x200+extrablock*Blocksize+Blocksize-4 || Bread(b, buf, 4) != 4) { print("bseek %d fail\n", 0x4C+4*i); goto Die; } extrablock = LONG(buf); } Break2:; if(oreaddir(f, 0, &rootdir) <= 0) goto Die; f->smapblock = rootdir.start; return f; Die: Bterm(b); free(f->blockmap); free(f); return nil; } void oleread(Req *r) { Odir *d; char *p; int e, n; long c; vlong o; o = r->ifcall.offset; d = r->fid->file->aux; if(d == nil) { respond(r, "cannot happen"); return; } c = r->ifcall.count; if(o >= d->size) { r->ofcall.count = 0; respond(r, nil); return; } if(o+c > d->size) c = d->size-o; /* * oreadfile returns so little data, it will * help to read as much as we can. */ e = c+o; n = 0; for(p=r->ofcall.data; oifcall.offset) respond(r, "error reading word file"); else { r->ofcall.count = o - r->ifcall.offset; respond(r, nil); } } Odir* copydir(Odir *d) { Odir *e; e = emalloc(sizeof(*d)); *e = *d; return e; } void filldir(File *t, Ofile *f, int dnum, int nrecur) { Odir d; int i; Rune rbuf[40]; char buf[UTFmax*nelem(rbuf)]; File *nt; if(dnum == 0xFFFFFFFF || oreaddir(f, dnum, &d) != 1) return; /* * i hope there are no broken files with * circular trees. i hate infinite loops. */ if(nrecur > 100) sysfatal("tree too large in office file: probably circular"); filldir(t, f, d.left, nrecur+1); /* add current tree entry */ runestrecpy(rbuf, rbuf+sizeof rbuf, d.name); for(i=0; rbuf[i]; i++) if(rbuf[i] == L' ') rbuf[i] = L'␣'; else if(rbuf[i] <= 0x20 || rbuf[i] == L'/' || (0x80 <= rbuf[i] && rbuf[i] <= 0x9F)) rbuf[i] = ':'; snprint(buf, sizeof buf, "%S", rbuf); if(d.dir == 0xFFFFFFFF) { /* make file */ nt = createfile(t, buf, nil, 0444, nil); if(nt ==nil) fprint(2, "nt nil: create %s: %r\n", buf); nt->aux = copydir(&d); nt->length = d.size; } else /* make directory */ nt = createfile(t, buf, nil, DMDIR|0777, nil); filldir(t, f, d.right, nrecur+1); if(d.dir != 0xFFFFFFFF) filldir(nt, f, d.dir, nrecur+1); closefile(nt); } Srv olesrv = { .read= oleread, }; void main(int argc, char **argv) { char *mtpt; Ofile *f; Odir d; mtpt = "/mnt/doc"; ARGBEGIN{ case 'm': mtpt = ARGF(); break; default: goto Usage; }ARGEND if(argc != 1) { Usage: fprint(2, "usage: olefs file\n"); exits("usage"); } f = oleopen(argv[0]); if(f == nil) { print("error: %r\n"); exits("open"); } // dumpdir(f, 0); if(oreaddir(f, 0, &d) != 1) { fprint(2, "oreaddir error: %r\n"); exits("oreaddir"); } olesrv.tree = alloctree(nil, nil, DMDIR|0777, nil); filldir(olesrv.tree->root, f, d.dir, 0); postmountsrv(&olesrv, nil, mtpt, MREPL); exits(0); }