"Initial commit to Gerrit"
[profile/ivi/libgsf.git] / gsf / gsf-outfile-msole.c
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * gsf-outfile-msole.c: 
4  *
5  * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2.1 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Outc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21
22 #include <gsf-config.h>
23 #include <gsf/gsf-outfile-impl.h>
24 #include <gsf/gsf-outfile-msole.h>
25 #include <gsf/gsf-impl-utils.h>
26 #include <gsf/gsf-msole-impl.h>
27 #include <gsf/gsf-utils.h>
28
29 #include <string.h>
30 #include <stdio.h>
31
32 static GObjectClass *parent_class;
33 static GsfOutputClass *gsf_output_class;
34
35 #undef G_LOG_DOMAIN
36 #define G_LOG_DOMAIN "libgsf:msole"
37
38 typedef enum { MSOLE_DIR, MSOLE_SMALL_BLOCK, MSOLE_BIG_BLOCK } MSOleOutfileType;
39
40 /* The most common values */
41 #define OLE_DEFAULT_THRESHOLD    0x1000
42 #define OLE_DEFAULT_BB_SIZE      (1 << OLE_DEFAULT_BB_SHIFT)
43 #define OLE_DEFAULT_SB_SIZE      (1 << OLE_DEFAULT_SB_SHIFT)
44
45 struct _GsfOutfileMSOle {
46         GsfOutfile parent;
47
48         GsfOutput       *sink;
49         GsfOutfileMSOle *root;
50
51         MSOleOutfileType type;
52         unsigned         first_block;
53         unsigned         blocks;
54         unsigned         child_index;
55
56         struct {
57                 unsigned shift;
58                 unsigned size;
59         } bb, sb;
60
61         union {
62                 struct {
63                         GSList    *children;
64                         GPtrArray *root_order;  /* only valid for the root */
65                 } dir;
66                 struct {
67                         guint8 *buf;
68                 } small_block;
69                 struct {
70                         size_t  start_offset;   /* in bytes */
71                 } big_block;
72         } content;
73         unsigned char clsid[16];                /* 16 byte GUID used by some apps */
74 };
75 typedef GsfOutfileClass GsfOutfileMSOleClass;
76
77 static void
78 gsf_outfile_msole_finalize (GObject *obj)
79 {
80         GsfOutfileMSOle *ole = GSF_OUTFILE_MSOLE (obj);
81         GsfOutput *output = GSF_OUTPUT (obj);
82
83         if (!gsf_output_is_closed (output))
84                 gsf_output_close (output);
85
86         if (ole->sink != NULL) {
87                 g_object_unref (G_OBJECT (ole->sink));
88                 ole->sink = NULL;
89         }
90         switch (ole->type) {
91         case MSOLE_DIR:
92                 g_slist_free (ole->content.dir.children);
93                 ole->content.dir.children = NULL;
94                 if (ole->content.dir.root_order != NULL)
95                         g_warning ("Finalizing a MSOle Outfile without closing it.");
96                 break;
97
98         case MSOLE_SMALL_BLOCK:
99                 g_free (ole->content.small_block.buf);
100                 ole->content.small_block.buf = NULL;
101                 break;
102
103         case MSOLE_BIG_BLOCK:
104                 break;
105         default :
106                 g_warning ("Unknown file type");
107         }
108         parent_class->finalize (obj);
109 }
110
111 static gboolean
112 gsf_outfile_msole_seek (GsfOutput *output, gsf_off_t offset,
113                         GSeekType whence)
114 {
115         GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
116
117         switch (whence) {
118         case G_SEEK_SET : break;
119         case G_SEEK_CUR : offset += output->cur_offset; break;
120         case G_SEEK_END : offset += output->cur_size;   break;
121         default :
122                 break; /*checked in GsfOutput wrapper */
123         }
124
125         switch (ole->type) {
126         case MSOLE_DIR:
127                 if (offset != 0) {
128                         g_warning ("Attempt to seek a directory");
129                         return FALSE;
130                 }
131                 return TRUE;
132
133         case MSOLE_SMALL_BLOCK:
134                 /* it is ok to seek past the big block threshold
135                  * we don't convert until they _write_ something
136                  */
137                 return TRUE;
138
139         case MSOLE_BIG_BLOCK:
140                 return gsf_output_seek (ole->sink,
141                         (gsf_off_t)(ole->content.big_block.start_offset + offset),
142                         G_SEEK_SET);
143
144         default :
145                 return FALSE;
146         }
147
148         return FALSE;
149 }
150
151 /* Globals to support variable OLE sector size.                                 */
152 /* 512 and 4096 bytes are the only known values for sector size on              */
153 /* Win2k/XP platforms.  Attempts to create OLE files on Win2k/XP with           */
154 /* other values using StgCreateStorageEx() fail with invalid parameter.         */
155 /* This code has been tested with 128,256,512,4096,8192 sizes for               */
156 /* libgsf read/write.  Interoperability with MS OLE32.DLL has been              */
157 /* tested with 512 and 4096 block size for filesizes up to 2GB.                 */
158
159 #define ZERO_PAD_BUF_SIZE 4096
160
161 /* static objects are zero-initialized as per C/C++ standards */
162 static guint8 const zero_buf [ZERO_PAD_BUF_SIZE];
163
164 /* Calculate the block of the current offset in the file.  A useful idiom is to
165  * pad_zero to move to the start of the next block, then get the block number.
166  * This avoids fence post type problems with partial blocks. */
167 static inline guint32
168 ole_cur_block (GsfOutfileMSOle const *ole)
169 {
170         return (gsf_output_tell (ole->sink) - OLE_HEADER_SIZE) >> ole->bb.shift;
171 }
172
173 static inline unsigned
174 ole_bytes_left_in_block (GsfOutfileMSOle *ole)
175 {
176         /* blocks are multiples of bb.size (the header is padded out to bb.size) */
177         unsigned r = gsf_output_tell (ole->sink) % ole->bb.size;
178         return (r != 0) ? (ole->bb.size - r) : 0;
179 }
180
181 static void
182 ole_pad_zero (GsfOutfileMSOle *ole)
183 {
184         /* no need to bounds check.  len will always be less than bb.size, and
185          * we already check that zero_buf is big enough at creation */
186         unsigned len = ole_bytes_left_in_block (ole);
187         if (len > 0)
188                 gsf_output_write (ole->sink, len, zero_buf);
189 }
190
191 /* Utility routine to generate a BAT for a file known to be sequential and
192  * continuous. */
193 static void
194 ole_write_bat (GsfOutput *sink, guint32 block, unsigned blocks)
195 {
196         guint8 buf [BAT_INDEX_SIZE];
197
198 /* FIXME FIXME FIXME  optimize this to dump a buffer in 1 step */
199         while (blocks-- > 1) {
200                 block++;
201                 GSF_LE_SET_GUINT32 (buf, block);
202                 gsf_output_write (sink, BAT_INDEX_SIZE, buf);
203         }
204         GSF_LE_SET_GUINT32 (buf, BAT_MAGIC_END_OF_CHAIN);
205         gsf_output_write (sink, BAT_INDEX_SIZE, buf);
206 }
207
208 static void
209 ole_write_const (GsfOutput *sink, guint32 value, unsigned n)
210 {
211         guint8 buf [BAT_INDEX_SIZE];
212
213         GSF_LE_SET_GUINT32 (buf, value);
214         while (n-- > 0)
215                 gsf_output_write (sink, BAT_INDEX_SIZE, buf);
216 }
217
218 static void
219 ole_pad_bat_unused (GsfOutfileMSOle *ole, unsigned residual)
220 {
221         ole_write_const (ole->sink, BAT_MAGIC_UNUSED, 
222                 (ole_bytes_left_in_block (ole) / BAT_INDEX_SIZE) - residual);
223 }
224
225 /* write the metadata (dirents, small block, xbats) and close the sink */
226 static gboolean
227 gsf_outfile_msole_close_root (GsfOutfileMSOle *ole)
228 {
229         GsfOutfile *tmp;
230         guint8  buf [OLE_HEADER_SIZE];
231         guint32 sbat_start, num_sbat, sb_data_start, sb_data_size, sb_data_blocks;
232         guint32 bat_start, num_bat, dirent_start, num_dirent_blocks, next, child_index;
233         unsigned i, j, blocks, num_xbat, xbat_pos;
234         gsf_off_t data_size;
235         unsigned metabat_size = ole->bb.size / BAT_INDEX_SIZE - 1;
236         GPtrArray *elem = ole->root->content.dir.root_order;
237
238         /* write small block data */
239         blocks = 0;
240         sb_data_start = ole_cur_block (ole);
241         data_size = gsf_output_tell (ole->sink);
242         for (i = 0 ; i < elem->len ; i++) {
243                 GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
244                 if (child->type == MSOLE_SMALL_BLOCK) {
245                         gsf_off_t size = gsf_output_size (GSF_OUTPUT (child));
246                         if (size > 0) {
247                                 child->blocks = ((size - 1) >> ole->sb.shift) + 1;
248                                 gsf_output_write (ole->sink,
249                                                   child->blocks << ole->sb.shift,
250                                                   child->content.small_block.buf);
251                                 child->first_block = blocks;
252                                 blocks += child->blocks;
253                         } else {
254                                 child->blocks = 0;
255                                 child->first_block = BAT_MAGIC_END_OF_CHAIN;
256                         }
257                 }
258         }
259         data_size = gsf_output_tell (ole->sink) - data_size;
260         sb_data_size = data_size;
261         if ((gsf_off_t) sb_data_size != data_size) {
262                 /* Check for overflow */
263                 g_warning ("File too big");
264                 return FALSE;
265         }
266         ole_pad_zero (ole);
267         sb_data_blocks = ole_cur_block (ole) - sb_data_start;
268
269         /* write small block BAT (the meta bat is in a file) */
270         sbat_start = ole_cur_block (ole);
271         for (i = 0 ; i < elem->len ; i++) {
272                 GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
273                 if (child->type == MSOLE_SMALL_BLOCK && child->blocks > 0)
274                         ole_write_bat (ole->sink, child->first_block, child->blocks);
275         }
276         ole_pad_bat_unused (ole, 0);
277         num_sbat = ole_cur_block (ole) - sbat_start;
278
279         /* write dirents */
280         dirent_start = ole_cur_block (ole);
281         for (i = 0 ; i < elem->len ; i++) {
282                 GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
283                 glong j, name_len = 0;
284
285                 memset (buf, 0, DIRENT_SIZE);
286
287                 /* Hard code 'Root Entry' for the root */
288                 if (i == 0 || gsf_output_name (GSF_OUTPUT (child)) != NULL) {
289                         char const *name = (i == 0)
290                                 ? "Root Entry" : gsf_output_name (GSF_OUTPUT (child));
291                         gunichar2 *name_utf16 = g_utf8_to_utf16 (name,
292                                 -1, NULL, &name_len, NULL);
293                         if (name_len >= DIRENT_MAX_NAME_SIZE)
294                                 name_len = DIRENT_MAX_NAME_SIZE-1;
295
296                         /* be wary about endianness */
297                         for (j = 0 ; j < name_len ; j++)
298                                 GSF_LE_SET_GUINT16 (buf + j*2, name_utf16 [j]);
299                         g_free (name_utf16);
300                         name_len++;
301                 }
302                 GSF_LE_SET_GUINT16 (buf + DIRENT_NAME_LEN, name_len*2);
303
304                 if (child->root == child) {
305                         GSF_LE_SET_GUINT8  (buf + DIRENT_TYPE,  DIRENT_TYPE_ROOTDIR);
306                         GSF_LE_SET_GUINT32 (buf + DIRENT_FIRSTBLOCK,
307                                 (sb_data_size > 0) ? sb_data_start : BAT_MAGIC_END_OF_CHAIN);
308                         GSF_LE_SET_GUINT32 (buf + DIRENT_FILE_SIZE, sb_data_size);
309                         memcpy (buf + DIRENT_CLSID, child->clsid, sizeof (child->clsid));
310                 } else if (child->type == MSOLE_DIR) {
311                         GSF_LE_SET_GUINT8 (buf + DIRENT_TYPE, DIRENT_TYPE_DIR);
312                         GSF_LE_SET_GUINT32 (buf + DIRENT_FIRSTBLOCK, BAT_MAGIC_END_OF_CHAIN);
313                         GSF_LE_SET_GUINT32 (buf + DIRENT_FILE_SIZE, 0);
314                         /* write the class id */
315                         memcpy (buf + DIRENT_CLSID, child->clsid, sizeof (child->clsid));
316                 } else {
317                         guint32 size = child->parent.parent.cur_size;
318
319                         if ((gsf_off_t) size != child->parent.parent.cur_size)
320                                 g_warning ("File too big");
321                         GSF_LE_SET_GUINT8 (buf + DIRENT_TYPE, DIRENT_TYPE_FILE);
322                         GSF_LE_SET_GUINT32 (buf + DIRENT_FIRSTBLOCK, child->first_block);
323                         GSF_LE_SET_GUINT32 (buf + DIRENT_FILE_SIZE, size);
324                 }
325                 /* make everything black (red == 0) */
326                 GSF_LE_SET_GUINT8  (buf + DIRENT_COLOUR, 1);
327
328                 tmp = gsf_output_container (GSF_OUTPUT (child));
329                 next = DIRENT_MAGIC_END;
330                 if (child->root != child && tmp != NULL) {
331                         GSList *ptr = GSF_OUTFILE_MSOLE (tmp)->content.dir.children;
332                         for (; ptr != NULL ; ptr = ptr->next)
333                                 if (ptr->data == child) {
334                                         if (ptr->next != NULL) {
335                                                 GsfOutfileMSOle *sibling = ptr->next->data;
336                                                 next = sibling->child_index;
337                                         }
338                                         break;
339                                 }
340                 }
341                 /* make linked list rather than tree, only use next */
342                 GSF_LE_SET_GUINT32 (buf + DIRENT_PREV, DIRENT_MAGIC_END);
343                 GSF_LE_SET_GUINT32 (buf + DIRENT_NEXT, next);
344
345                 child_index = DIRENT_MAGIC_END;
346                 if (child->type == MSOLE_DIR && child->content.dir.children != NULL) {
347                         GsfOutfileMSOle *first = child->content.dir.children->data;
348                         child_index = first->child_index;
349                 }
350                 GSF_LE_SET_GUINT32 (buf + DIRENT_CHILD, child_index);
351
352                 gsf_output_write (ole->sink, DIRENT_SIZE, buf);
353         }
354         ole_pad_zero (ole);
355         num_dirent_blocks = ole_cur_block (ole) - dirent_start;
356
357         /* write BAT */
358         bat_start = ole_cur_block (ole);
359         for (i = 0 ; i < elem->len ; i++) {
360                 GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
361                 if (child->type == MSOLE_BIG_BLOCK)
362                         ole_write_bat (ole->sink, child->first_block, child->blocks);
363         }
364         if (sb_data_blocks > 0)
365                 ole_write_bat (ole->sink, sb_data_start, sb_data_blocks);
366         if (num_sbat > 0)
367                 ole_write_bat (ole->sink, sbat_start, num_sbat);
368         ole_write_bat (ole->sink, dirent_start, num_dirent_blocks);
369
370         /* List the BAT and meta-BAT blocks in the BAT.  Doing this may
371          * increase the size of the bat and hence the metabat, so be
372          * prepared to iterate.
373          */
374         num_bat = 0;
375         num_xbat = 0;
376 recalc_bat_bat :
377         i = ((ole->sink->cur_size
378               + BAT_INDEX_SIZE * (num_bat + num_xbat)
379               - OLE_HEADER_SIZE - 1) >> ole->bb.shift) + 1;
380         i -= bat_start;
381         if (num_bat != i) {
382                 num_bat = i;
383                 goto recalc_bat_bat;
384         }
385         i = 0;
386         if (num_bat > OLE_HEADER_METABAT_SIZE)
387                 i = 1 + ((num_bat - OLE_HEADER_METABAT_SIZE - 1)
388                          / metabat_size);
389         if (num_xbat != i) {
390                 num_xbat = i;
391                 goto recalc_bat_bat;
392         }
393
394         ole_write_const (ole->sink, BAT_MAGIC_BAT, num_bat);
395         ole_write_const (ole->sink, BAT_MAGIC_METABAT, num_xbat);
396         ole_pad_bat_unused (ole, 0);
397
398         if (num_xbat > 0) {
399                 xbat_pos = ole_cur_block (ole);
400                 blocks = OLE_HEADER_METABAT_SIZE;
401         } else {
402                 xbat_pos = BAT_MAGIC_END_OF_CHAIN;
403                 blocks = num_bat;
404         }
405
406         /* fix up the header */
407         if (ole->bb.size == 4096) {
408                 /* set _cSectDir for 4k sector files */
409                 GSF_LE_SET_GUINT32 (buf,   num_dirent_blocks);
410                 gsf_output_seek (ole->sink,
411                         (gsf_off_t) OLE_HEADER_CSECTDIR, G_SEEK_SET);
412                 gsf_output_write (ole->sink, 4, buf);
413         }
414         GSF_LE_SET_GUINT32 (buf,   num_bat);
415         GSF_LE_SET_GUINT32 (buf+4, dirent_start);
416         gsf_output_seek (ole->sink,
417                 (gsf_off_t) OLE_HEADER_NUM_BAT, G_SEEK_SET);
418         gsf_output_write (ole->sink, 8, buf);
419
420         GSF_LE_SET_GUINT32 (buf+0x0,
421                 (num_sbat > 0) ? sbat_start : BAT_MAGIC_END_OF_CHAIN);
422         GSF_LE_SET_GUINT32 (buf+0x4, num_sbat);
423         GSF_LE_SET_GUINT32 (buf+0x8, xbat_pos);
424         GSF_LE_SET_GUINT32 (buf+0xc, num_xbat);
425         gsf_output_seek (ole->sink, (gsf_off_t) OLE_HEADER_SBAT_START,
426                          G_SEEK_SET);
427         gsf_output_write (ole->sink, 0x10, buf);
428
429         /* write initial Meta-BAT */
430         for (i = 0 ; i < blocks ; i++) {
431                 GSF_LE_SET_GUINT32 (buf, bat_start + i);
432                 gsf_output_write (ole->sink, BAT_INDEX_SIZE, buf);
433         }
434
435         /* write extended Meta-BAT */
436         if (num_xbat > 0) {
437                 gsf_output_seek (ole->sink, 0, G_SEEK_END);
438                 for (i = 0 ; i++ < num_xbat ; ) {
439                         bat_start += blocks;
440                         num_bat   -= blocks;
441                         blocks = (num_bat > metabat_size) ? metabat_size : num_bat;
442                         for (j = 0 ; j < blocks ; j++) {
443                                 GSF_LE_SET_GUINT32 (buf, bat_start + j);
444                                 gsf_output_write (ole->sink, BAT_INDEX_SIZE, buf);
445                         }
446
447                         if (i == num_xbat) {
448                                 ole_pad_bat_unused (ole, 1);
449                                 xbat_pos = BAT_MAGIC_END_OF_CHAIN;
450                         } else
451                                 xbat_pos++;
452                         GSF_LE_SET_GUINT32 (buf, xbat_pos);
453                         gsf_output_write (ole->sink, BAT_INDEX_SIZE, buf);
454                 }
455         }
456
457         /* free the children */
458         for (i = 0 ; i < elem->len ; i++)
459                 g_object_unref (G_OBJECT (g_ptr_array_index (elem, i)));
460         g_ptr_array_free (elem, TRUE);
461         ole->content.dir.root_order = NULL;
462
463         return gsf_output_close (ole->sink);
464 }
465
466 static gboolean
467 gsf_outfile_msole_close (GsfOutput *output)
468 {
469         GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
470
471         if (gsf_output_container (output) == NULL)      /* The root dir */
472                 return gsf_outfile_msole_close_root (ole);
473
474         if (ole->type == MSOLE_BIG_BLOCK) {
475                 gsf_outfile_msole_seek (output, 0, G_SEEK_END);
476                 ole_pad_zero (ole);
477                 ole->blocks = ole_cur_block (ole) - ole->first_block;
478                 return gsf_output_unwrap (G_OBJECT (output), ole->sink);
479         }
480
481         return TRUE;
482 }
483
484 static gboolean
485 gsf_outfile_msole_write (GsfOutput *output,
486                          size_t num_bytes, guint8 const *data)
487 {
488         GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
489         size_t wsize;
490
491         g_return_val_if_fail (ole->type != MSOLE_DIR, FALSE);
492         if (ole->type == MSOLE_SMALL_BLOCK) {
493                 gboolean ok;
494                 guint8 *buf;
495                 gsf_off_t start_offset;
496
497                 if ((output->cur_offset + num_bytes) < OLE_DEFAULT_THRESHOLD) {
498                         memcpy (ole->content.small_block.buf + output->cur_offset,
499                                 data, num_bytes);
500                         return TRUE;
501                 }
502                 ok = gsf_output_wrap (G_OBJECT (output), ole->sink);
503                 if (!ok)
504                         return FALSE;
505
506                 buf = ole->content.small_block.buf;
507                 ole->content.small_block.buf = NULL;
508                 start_offset = gsf_output_tell (ole->sink);
509                 ole->content.big_block.start_offset = start_offset;
510                 if ((gsf_off_t) ole->content.big_block.start_offset
511                     != start_offset) {
512                         /* Check for overflow */
513                         g_warning ("File too big");
514                         return FALSE;
515                 }
516                 
517                 ole->first_block = ole_cur_block (ole);
518                 ole->type = MSOLE_BIG_BLOCK;
519                 wsize = output->cur_size;
520                 if ((gsf_off_t) wsize != output->cur_size) {
521                         /* Check for overflow */
522                         g_warning ("File too big");
523                         return FALSE;
524                 }
525                 gsf_output_write (ole->sink, wsize, buf);
526                 g_free (buf);
527         }
528
529         g_return_val_if_fail (ole->type == MSOLE_BIG_BLOCK, FALSE);
530
531         gsf_output_write (ole->sink, num_bytes, data);
532
533         return TRUE;
534 }
535
536 static gsf_off_t gsf_outfile_msole_vprintf (GsfOutput *output,
537         char const *format, va_list args) G_GNUC_PRINTF (2, 0);
538
539 static gsf_off_t
540 gsf_outfile_msole_vprintf (GsfOutput *output, char const *format, va_list args)
541 {
542         GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
543
544         /* An optimization. */
545         if (ole->type == MSOLE_BIG_BLOCK)
546                 return gsf_output_vprintf (ole->sink, format, args);
547
548         /* In other cases, use the gsf_output_real_vprintf fallback method.
549          * (This eventually calls gsf_outfile_msole_write, which will also
550          * check that ole->type != MSOLE_DIR.)
551          */
552         return gsf_output_class->Vprintf (output, format, args);
553 }
554
555
556 static void
557 ole_register_child (GsfOutfileMSOle *root, GsfOutfileMSOle *child)
558 {
559         child->root = root;
560         g_object_ref (G_OBJECT (child));
561         child->child_index = root->content.dir.root_order->len;
562         g_ptr_array_add (root->content.dir.root_order, child);
563 }
564
565 static gint
566 ole_name_cmp (GsfOutfileMSOle const *a, GsfOutfileMSOle const *b)
567 {
568         /* According to the docs length is more important than lexical order */
569         char const *a_name = gsf_output_name ((GsfOutput const *)a);
570         char const *b_name = gsf_output_name ((GsfOutput const *)b);
571
572         /* be anal */
573         if (a_name == NULL)
574                 return (b_name == NULL) ? 0 : -1;
575         else if (b_name == NULL)
576                 return 1;
577         else {
578                 unsigned a_len = g_utf8_strlen (a_name, -1);
579                 unsigned b_len = g_utf8_strlen (b_name, -1);
580
581                 if (a_len != b_len)
582                         return a_len - b_len;
583                 return g_utf8_collate (a_name, b_name);
584         }
585 }
586
587 static void
588 gsf_outfile_msole_set_block_shift (GsfOutfileMSOle *ole,
589                                    unsigned bb_shift, unsigned sb_shift)
590 {
591         ole->bb.shift = bb_shift;
592         ole->bb.size  = (1 << ole->bb.shift);
593         ole->sb.shift = sb_shift;
594         ole->sb.size  = (1 << ole->sb.shift);
595 }
596
597 static GsfOutput *
598 gsf_outfile_msole_new_child (GsfOutfile *parent,
599                              char const *name, gboolean is_dir,
600                              char const *first_property_name, va_list args)
601 {
602         GsfOutfileMSOle *ole_parent = (GsfOutfileMSOle *)parent;
603         GsfOutfileMSOle *child;
604
605         g_return_val_if_fail (ole_parent != NULL, NULL);
606         g_return_val_if_fail (ole_parent->type == MSOLE_DIR, NULL);
607
608         child = (GsfOutfileMSOle *)g_object_new_valist (
609                 GSF_OUTFILE_MSOLE_TYPE, first_property_name, args);
610         if (is_dir) {
611                 child->type = MSOLE_DIR;
612                 child->content.dir.children = NULL;
613         } else {
614                 /* start as small block */
615                 child->type = MSOLE_SMALL_BLOCK;
616                 child->content.small_block.buf = g_new0 (guint8, OLE_DEFAULT_THRESHOLD);
617         }
618         g_object_ref (G_OBJECT (ole_parent->sink));
619         child->sink   = ole_parent->sink;
620         child->root   = ole_parent->root;
621         gsf_outfile_msole_set_block_shift (child,
622                 ole_parent->bb.shift, ole_parent->sb.shift); 
623         gsf_output_set_name (GSF_OUTPUT (child), name);
624         gsf_output_set_container (GSF_OUTPUT (child), parent);
625
626         ole_parent->content.dir.children = g_slist_insert_sorted (
627                 ole_parent->content.dir.children, child,
628                 (GCompareFunc)ole_name_cmp);
629         ole_register_child (ole_parent->root, child);
630
631         return GSF_OUTPUT (child);
632 }
633
634 static void
635 gsf_outfile_msole_init (GObject *obj)
636 {
637         GsfOutfileMSOle *ole = GSF_OUTFILE_MSOLE (obj);
638
639         ole->sink   = NULL;
640         ole->root   = NULL;
641         ole->type   = MSOLE_DIR;
642
643         gsf_outfile_msole_set_block_shift (ole, 
644                 OLE_DEFAULT_BB_SHIFT, OLE_DEFAULT_SB_SHIFT);
645
646         ole->content.dir.children = NULL;
647         ole->content.dir.root_order = NULL;
648         memset (ole->clsid, 0, sizeof (ole->clsid));
649 }
650
651 static void
652 gsf_outfile_msole_class_init (GObjectClass *gobject_class)
653 {
654         GsfOutputClass  *output_class  = GSF_OUTPUT_CLASS (gobject_class);
655         GsfOutfileClass *outfile_class = GSF_OUTFILE_CLASS (gobject_class);
656
657         gobject_class->finalize         = gsf_outfile_msole_finalize;
658         output_class->Close             = gsf_outfile_msole_close;
659         output_class->Seek              = gsf_outfile_msole_seek;
660         output_class->Write             = gsf_outfile_msole_write;
661         output_class->Vprintf           = gsf_outfile_msole_vprintf;
662         outfile_class->new_child        = gsf_outfile_msole_new_child;
663
664         parent_class = g_type_class_peek_parent (gobject_class);
665         gsf_output_class = g_type_class_peek (GSF_OUTPUT_TYPE);
666 }
667
668 GSF_CLASS (GsfOutfileMSOle, gsf_outfile_msole,
669            gsf_outfile_msole_class_init, gsf_outfile_msole_init,
670            GSF_OUTFILE_TYPE)
671
672 /* returns the number of times 1 must be shifted left to reach value */
673 static unsigned
674 compute_shift (unsigned value)
675 {
676         unsigned i = 0;
677         while ((value >> i) > 1)
678                 i++;
679         return i;
680 }
681
682 /**
683  * gsf_outfile_msole_new_full :
684  * @sink : a #GsfOutput to hold the OLE2 file.
685  * @bb_size : size of large blocks.
686  * @sb_size : size of small blocks.
687  *
688  * Creates the root directory of an MS OLE file and manages the addition of
689  * children.
690  *
691  * <note>This adds a reference to @sink.</note>
692  *
693  * Returns: the new ole file handler.
694  **/
695 GsfOutfile *
696 gsf_outfile_msole_new_full (GsfOutput *sink, guint bb_size, guint sb_size)
697 {
698         static guint8 const default_header [] = {
699 /* 0x00 */      0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
700 /* 0x08 */      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
701 /* 0x18 */      0x3e, 0x00, 0x03, 0x00, 0xfe, 0xff, 0x09, 0x00,
702 /* 0x20 */      0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
703 /* 0x28 */      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
704 /* 0x30 */      0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
705 /* 0x38 */      0x00, 0x10, 0x00, 0x00 /* 0x3c-0x4b: filled on close */
706         };
707         guint8 *buf;
708         GsfOutfileMSOle *ole;
709
710         g_return_val_if_fail (GSF_IS_OUTPUT (sink), NULL);
711
712         ole = g_object_new (GSF_OUTFILE_MSOLE_TYPE, NULL);
713         if (G_UNLIKELY (NULL == ole)) return NULL;
714
715         g_object_ref (G_OBJECT (sink));
716         ole->sink = sink;
717         ole->type = MSOLE_DIR;
718         ole->content.dir.root_order = g_ptr_array_new ();
719         ole_register_child (ole, ole);
720
721         gsf_outfile_msole_set_block_shift (ole,
722                 compute_shift (bb_size), compute_shift (sb_size));
723         if (ole->bb.size != bb_size ||
724             ole->sb.size != sb_size ||
725             bb_size <= sb_size ||
726             bb_size < DIRENT_SIZE ||
727             sb_size < 8 ||
728             ZERO_PAD_BUF_SIZE < ole->bb.size) {
729                 if (ZERO_PAD_BUF_SIZE < ole->bb.size)
730                         g_warning ("Block size is too big, failing back to defaults.");
731                 else
732                         g_warning ("Incorrect block sizes, failing back to defaults.");
733                 gsf_outfile_msole_set_block_shift (ole,
734                         OLE_DEFAULT_BB_SHIFT, OLE_DEFAULT_SB_SHIFT);
735         }
736
737         /* The names are the same */
738         gsf_output_set_name (GSF_OUTPUT (ole), gsf_output_name (sink));
739         gsf_output_set_container (GSF_OUTPUT (ole), NULL);
740
741         /* build the header */
742         buf = g_new (guint8, OLE_HEADER_SIZE);
743         memcpy (buf, default_header, sizeof (default_header));
744         memset (buf + sizeof (default_header), 0xff,
745                 OLE_HEADER_SIZE - sizeof (default_header));
746         GSF_LE_SET_GUINT16 (buf + OLE_HEADER_BB_SHIFT, ole->bb.shift);
747         GSF_LE_SET_GUINT16 (buf + OLE_HEADER_SB_SHIFT, ole->sb.shift);
748         /* 4k sector OLE files seen in the wild have version 4 */
749         if (ole->bb.size == 4096)
750                 GSF_LE_SET_GUINT16 (buf + OLE_HEADER_MAJOR_VER, 4);
751         gsf_output_write (sink, OLE_HEADER_SIZE, buf);
752         g_free (buf);
753
754         /* header must be padded out to bb.size with zeros */
755         ole_pad_zero(ole);
756
757         return GSF_OUTFILE (ole);
758 }
759
760 /**
761  * gsf_outfile_msole_new :
762  * @sink : a #GsfOutput to hold the OLE2 file
763  *
764  * Creates the root directory of an MS OLE file and manages the addition of
765  * children.
766  *
767  * <note>This adds a reference to @sink.</note>
768  *
769  * Returns: the new ole file handler.
770  **/
771 GsfOutfile *
772 gsf_outfile_msole_new (GsfOutput *sink)
773 {
774         return gsf_outfile_msole_new_full (sink, 
775                 OLE_DEFAULT_BB_SIZE, OLE_DEFAULT_SB_SIZE);
776 }
777
778 /**
779  * gsf_outfile_msole_set_class_id :
780  * @ole: a #GsfOutfileMSOle
781  * @clsid: 16 byte identifier (often a GUID in MS Windows apps)
782  *
783  * Write @clsid to the directory associated with @ole.
784  *
785  * Returns: TRUE on success.
786  **/
787 gboolean
788 gsf_outfile_msole_set_class_id (GsfOutfileMSOle *ole, guint8 const *clsid)
789 {
790         g_return_val_if_fail (ole != NULL && ole->type == MSOLE_DIR, FALSE);
791         memcpy (ole->clsid, clsid, sizeof (ole->clsid));
792         return TRUE;
793 }