1 #include <u.h> 2 #include <libc.h> 3 #include <bio.h> 4 #include <auth.h> 5 #include <fcall.h> 6 #include <thread.h> 7 #include <9p.h> 8 9 /* little endian */ 10 #define SHORT(p) (((uchar*)(p))[0] | (((uchar*)(p))[1] << 8)) 11 #define LONG(p) ((ulong)SHORT(p) |(((ulong)SHORT((p)+2)) << 16)) 12 13 typedef struct Ofile Ofile; 14 typedef struct Odir Odir; 15 16 enum { 17 /* special block map entries */ 18 Bspecial = 0xFFFFFFFD, 19 Bendchain = 0xFFFFFFFE, 20 Bunused = 0xFFFFFFFF, 21 22 Blocksize = 0x200, 23 24 Odirsize = 0x80, 25 26 /* Odir types */ 27 Tstorage = 1, 28 Tstream = 2, 29 Troot = 5, 30 }; 31 32 /* 33 * the file consists of chains of blocks of size 0x200. 34 * to find what block follows block n, you look at 35 * blockmap[n]. that block follows it unless it is Bspecial 36 * or Bendchain. 37 * 38 * it's like the MS-DOS file system allocation tables. 39 */ 40 struct Ofile { 41 Biobuf *b; 42 ulong nblock; 43 ulong *blockmap; 44 ulong rootblock; 45 ulong smapblock; 46 ulong *smallmap; 47 }; 48 49 /* Odir headers are found in directory listings in the Olefile */ 50 /* prev and next form a binary tree of directory entries */ 51 struct Odir { 52 Ofile *f; 53 Rune name[32+1]; 54 uchar type; 55 uchar isroot; 56 ulong left; 57 ulong right; 58 ulong dir; 59 ulong start; 60 ulong size; 61 }; 62 63 void* 64 emalloc(ulong sz) 65 { 66 void *v; 67 68 v = malloc(sz); 69 assert(v != nil); 70 return v; 71 } 72 73 int 74 convM2OD(Odir *f, void *buf, int nbuf) 75 { 76 int i; 77 char *p; 78 int len; 79 80 if(nbuf < Odirsize) 81 return -1; 82 83 /* 84 * the short at 0x40 is the length of the name. 85 * when zero, it means there is no Odir here. 86 */ 87 p = buf; 88 len = SHORT(p+0x40); 89 if(len == 0) 90 return 0; 91 92 if(len > 32) /* shouldn't happen */ 93 len = 32; 94 95 for(i=0; i<len; i++) 96 f->name[i] = SHORT(p+i*2); 97 f->name[len] = 0; 98 99 f->type = p[0x42]; 100 f->left = LONG(p+0x44); 101 f->right = LONG(p+0x48); 102 f->dir = LONG(p+0x4C); 103 f->start = LONG(p+0x74); 104 f->size = LONG(p+0x78); 105 106 /* BUG: grab time in ms format from here */ 107 108 return 1; 109 } 110 111 int 112 oreadblock(Ofile *f, int block, ulong off, char *buf, int nbuf) 113 { 114 int n; 115 116 if(block < 0 || block >= f->nblock) { 117 werrstr("attempt to read %x/%lux\n", block, f->nblock); 118 return -1; 119 } 120 121 if(off >= Blocksize){ 122 print("offset too far into block\n"); 123 return 0; 124 } 125 126 if(off+nbuf > Blocksize) 127 nbuf = Blocksize-off; 128 129 /* blocks start numbering at -1 [sic] */ 130 off += (block+1)*Blocksize; 131 132 if(Bseek(f->b, off, 0) != off){ 133 print("seek failed\n"); 134 return -1; 135 } 136 137 n = Bread(f->b, buf, nbuf); 138 if(n < 0) 139 print("Bread failed: %r"); 140 return n; 141 } 142 143 int 144 chainlen(Ofile *f, ulong start) 145 { 146 int i; 147 for(i=0; start < 0xFFFF0000; i++) 148 start = f->blockmap[start]; 149 150 return i; 151 } 152 153 /* 154 * read nbuf bytes starting at offset off from the 155 * chain whose first block is block. the chain is linked 156 * together via the blockmap as described above, 157 * like the MS-DOS file allocation tables. 158 */ 159 int 160 oreadchain(Ofile *f, ulong block, int off, char *buf, int nbuf) 161 { 162 int i; 163 int offblock; 164 165 offblock = off/Blocksize; 166 for(i=0; i<offblock && block < 0xFFFF0000; i++) 167 block = f->blockmap[block]; 168 return oreadblock(f, block, off%Blocksize, buf, nbuf); 169 } 170 171 int 172 oreadfile(Odir *d, int off, char *buf, int nbuf) 173 { 174 /* 175 * if d->size < 0x1000 then d->start refers 176 * to a small depot block, else a big one. 177 * if this is the root entry, it's a big one 178 * no matter what. 179 */ 180 181 if(off >= d->size) 182 return 0; 183 if(off+nbuf > d->size) 184 nbuf = d->size-off; 185 186 if(d->size >= 0x1000 187 || memcmp(d->name, L"Root Entry", 11*sizeof(Rune)) == 0) 188 return oreadchain(d->f, d->start, off, buf, nbuf); 189 else { /* small block */ 190 off += d->start*64; 191 return oreadchain(d->f, d->f->smapblock, off, buf, nbuf); 192 } 193 } 194 195 int 196 oreaddir(Ofile *f, int entry, Odir *d) 197 { 198 char buf[Odirsize]; 199 200 if(oreadchain(f, f->rootblock, entry*Odirsize, buf, Odirsize) != Odirsize) 201 return -1; 202 203 d->f = f; 204 return convM2OD(d, buf, Odirsize); 205 } 206 207 void 208 dumpdir(Ofile *f, ulong dnum) 209 { 210 Odir d; 211 212 if(oreaddir(f, dnum, &d) != 1) { 213 fprint(2, "dumpdir %lux failed\n", dnum); 214 return; 215 } 216 217 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); 218 if(d.left != (ulong)-1) 219 dumpdir(f, d.left); 220 if(d.right != (ulong)-1) 221 dumpdir(f, d.right); 222 if(d.dir != (ulong)-1) 223 dumpdir(f, d.dir); 224 } 225 226 Ofile* 227 oleopen(char *fn) 228 { 229 int i, j, k, block; 230 int ndepot; 231 ulong u; 232 Odir rootdir; 233 ulong extrablock; 234 uchar buf[Blocksize]; 235 236 Ofile *f; 237 Biobuf *b; 238 static char magic[] = { 239 0xD0, 0xCF, 0x11, 0xE0, 240 0xA1, 0xB1, 0x1A, 0xE1 241 }; 242 243 b = Bopen(fn, OREAD); 244 if(b == nil) 245 return nil; 246 247 /* the first bytes are magic */ 248 if(Bread(b, buf, sizeof magic) != sizeof magic 249 || memcmp(buf, magic, sizeof magic) != 0) { 250 Bterm(b); 251 werrstr("bad magic: not OLE file"); 252 return nil; 253 } 254 255 f = emalloc(sizeof *f); 256 f->b = b; 257 258 /* 259 * the header contains a list of depots, which are 260 * block maps. we assimilate them into one large map, 261 * kept in main memory. 262 */ 263 Bseek(b, 0, 0); 264 if(Bread(b, buf, Blocksize) != Blocksize) { 265 Bterm(b); 266 free(f); 267 print("short read\n"); 268 return nil; 269 } 270 271 ndepot = LONG(buf+0x2C); 272 f->nblock = ndepot*(Blocksize/4); 273 // fprint(2, "ndepot = %d f->nblock = %lud\n", ndepot, f->nblock); 274 f->rootblock = LONG(buf+0x30); 275 f->smapblock = LONG(buf+0x3C); 276 f->blockmap = emalloc(sizeof(f->blockmap[0])*f->nblock); 277 extrablock = LONG(buf+0x44); 278 279 u = 0; 280 281 /* the big block map fills to the end of the first 512-byte block */ 282 for(i=0; i<ndepot && i<(0x200-0x4C)/4; i++) { 283 if(Bseek(b, 0x4C+4*i, 0) != 0x4C+4*i 284 || Bread(b, buf, 4) != 4) { 285 print("bseek %d fail\n", 0x4C+4*i); 286 goto Die; 287 } 288 block = LONG(buf); 289 if((ulong)block == Bendchain) { 290 ndepot = i; 291 f->nblock = ndepot*(Blocksize/4); 292 break; 293 } 294 295 if(Bseek(b, (block+1)*Blocksize, 0) != (block+1)*Blocksize) { 296 print("Xbseek %d fail\n", (block+1)*Blocksize); 297 goto Die; 298 } 299 for(j=0; j<Blocksize/4; j++) { 300 if(Bread(b, buf, 4) != 4) { 301 print("Bread fail seek block %x, %d i %d ndepot %d\n", block, (block+1)*Blocksize, i, ndepot); 302 goto Die; 303 } 304 f->blockmap[u++] = LONG(buf); 305 } 306 } 307 /* 308 * if the first block can't hold it, it continues in the block at LONG(hdr+0x44). 309 * if that in turn is not big enough, there's a next block number at the end of 310 * each block. 311 */ 312 while(i < ndepot) { 313 for(k=0; k<(0x200-4)/4 && i<ndepot; i++, k++) { 314 if(Bseek(b, 0x200+extrablock*Blocksize+4*i, 0) != 0x200+extrablock*0x200+4*i 315 || Bread(b, buf, 4) != 4) { 316 print("bseek %d fail\n", 0x4C+4*i); 317 goto Die; 318 } 319 block = LONG(buf); 320 if((ulong)block == Bendchain) { 321 ndepot = i; 322 f->nblock = ndepot*(Blocksize/4); 323 goto Break2; 324 } 325 326 if(Bseek(b, (block+1)*Blocksize, 0) != (block+1)*Blocksize) { 327 print("Xbseek %d fail\n", (block+1)*Blocksize); 328 goto Die; 329 } 330 for(j=0; j<Blocksize/4; j++) { 331 if(Bread(b, buf, 4) != 4) { 332 print("Bread fail seek block %x, %d i %d ndepot %d\n", block, (block+1)*Blocksize, i, ndepot); 333 goto Die; 334 } 335 f->blockmap[u++] = LONG(buf); 336 } 337 } 338 if(Bseek(b, 0x200+extrablock*Blocksize+Blocksize-4, 0) != 0x200+extrablock*Blocksize+Blocksize-4 339 || Bread(b, buf, 4) != 4) { 340 print("bseek %d fail\n", 0x4C+4*i); 341 goto Die; 342 } 343 extrablock = LONG(buf); 344 } 345 Break2:; 346 347 if(oreaddir(f, 0, &rootdir) <= 0){ 348 print("oreaddir could not read root\n"); 349 goto Die; 350 } 351 352 f->smapblock = rootdir.start; 353 return f; 354 355 Die: 356 Bterm(b); 357 free(f->blockmap); 358 free(f); 359 return nil; 360 } 361 362 void 363 oleread(Req *r) 364 { 365 Odir *d; 366 char *p; 367 int e, n; 368 long c; 369 vlong o; 370 371 o = r->ifcall.offset; 372 d = r->fid->file->aux; 373 if(d == nil) { 374 respond(r, "cannot happen"); 375 return; 376 } 377 378 c = r->ifcall.count; 379 380 if(o >= d->size) { 381 r->ofcall.count = 0; 382 respond(r, nil); 383 return; 384 } 385 386 if(o+c > d->size) 387 c = d->size-o; 388 389 /* 390 * oreadfile returns so little data, it will 391 * help to read as much as we can. 392 */ 393 e = c+o; 394 n = 0; 395 for(p=r->ofcall.data; o<e; o+=n, p+=n) { 396 n = oreadfile(d, o, p, e-o); 397 if(n <= 0) 398 break; 399 } 400 401 if(n == -1 && o == r->ifcall.offset) 402 respond(r, "error reading word file"); 403 else { 404 r->ofcall.count = o - r->ifcall.offset; 405 respond(r, nil); 406 } 407 } 408 409 Odir* 410 copydir(Odir *d) 411 { 412 Odir *e; 413 414 e = emalloc(sizeof(*d)); 415 *e = *d; 416 return e; 417 } 418 419 void 420 filldir(File *t, Ofile *f, int dnum, int nrecur) 421 { 422 Odir d; 423 int i; 424 Rune rbuf[40]; 425 char buf[UTFmax*nelem(rbuf)]; 426 File *nt; 427 428 if(dnum == 0xFFFFFFFF || oreaddir(f, dnum, &d) != 1) 429 return; 430 431 /* 432 * i hope there are no broken files with 433 * circular trees. i hate infinite loops. 434 */ 435 if(nrecur > 100) 436 sysfatal("tree too large in office file: probably circular"); 437 438 filldir(t, f, d.left, nrecur+1); 439 440 /* add current tree entry */ 441 runestrecpy(rbuf, rbuf+sizeof rbuf, d.name); 442 for(i=0; rbuf[i]; i++) 443 if(rbuf[i] == L' ') 444 rbuf[i] = L'␣'; 445 else if(rbuf[i] <= 0x20 || rbuf[i] == L'/' 446 || (0x80 <= rbuf[i] && rbuf[i] <= 0x9F)) 447 rbuf[i] = ':'; 448 449 snprint(buf, sizeof buf, "%S", rbuf); 450 451 if(d.dir == 0xFFFFFFFF) { 452 /* make file */ 453 nt = createfile(t, buf, nil, 0444, nil); 454 if(nt == nil) 455 sysfatal("nt nil: create %s: %r", buf); 456 nt->aux = copydir(&d); 457 nt->length = d.size; 458 } else /* make directory */ 459 nt = createfile(t, buf, nil, DMDIR|0777, nil); 460 461 filldir(t, f, d.right, nrecur+1); 462 463 if(d.dir != 0xFFFFFFFF) 464 filldir(nt, f, d.dir, nrecur+1); 465 466 closefile(nt); 467 } 468 469 Srv olesrv = { 470 .read= oleread, 471 }; 472 473 void 474 main(int argc, char **argv) 475 { 476 char *mtpt; 477 Ofile *f; 478 Odir d; 479 480 mtpt = "/mnt/doc"; 481 ARGBEGIN{ 482 case 'm': 483 mtpt = ARGF(); 484 break; 485 486 default: 487 goto Usage; 488 }ARGEND 489 490 if(argc != 1) { 491 Usage: 492 fprint(2, "usage: olefs file\n"); 493 exits("usage"); 494 } 495 496 f = oleopen(argv[0]); 497 if(f == nil) { 498 print("error opening %s: %r\n", argv[0]); 499 exits("open"); 500 } 501 502 // dumpdir(f, 0); 503 504 if(oreaddir(f, 0, &d) != 1) { 505 fprint(2, "oreaddir error: %r\n"); 506 exits("oreaddir"); 507 } 508 509 olesrv.tree = alloctree(nil, nil, DMDIR|0777, nil); 510 filldir(olesrv.tree->root, f, d.dir, 0); 511 postmountsrv(&olesrv, nil, mtpt, MREPL); 512 exits(0); 513 } 514