]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/aux/olefs.c
exec(2): fix prototypes
[plan9front.git] / sys / src / cmd / aux / olefs.c
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 }