various: You cannot use the destination of snprintf as one of the srcs, undefined
[oweals/cde.git] / cde / programs / dthelp / dthelpgen / helpgen.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $TOG: helpgen.c /main/8 1998/04/20 12:52:36 mgreess $ */
24 #include <dirent.h>
25 #include <errno.h>
26 #include <locale.h>
27 #include <nl_types.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/param.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <X11/Xlib.h>
38 #include <X11/Xresource.h>
39 #include <X11/Intrinsic.h>
40
41 #include <Dt/Help.h>
42 #include <Dt/EnvControlP.h>
43
44 #include "HelpP.h"        /* in DtHelp library */
45 #include "GenUtilsP.h"    /* in DtHelp library */
46 #include "ObsoleteP.h"    /* in DtHelp library */
47 #include "bufioI.h"       /* for AccessI.h     */
48 #include "Access.h"       /* in DtHelp library */
49 #include "AccessP.h"      /* in DtHelp library */
50 #include "AccessI.h"      /* in DtHelp library */
51 #include "AccessCCDFI.h"  /* in DtHelp library */
52 #include "StringFuncsI.h" /* in DtHelp library */
53
54 #ifdef _AIX
55 #include <LocaleXlate.h>
56 #endif
57
58 #ifndef NL_CAT_LOCALE
59 static const int NL_CAT_LOCALE = 0;
60 #endif
61
62 #ifndef CDE_INSTALLATION_TOP
63 #define CDE_INSTALLATION_TOP "/usr/dt"
64 #endif
65
66 #ifndef CDE_CONFIGURATION_TOP
67 #define CDE_CONFIGURATION_TOP "/etc/dt"
68 #endif
69
70 #ifndef DtSYS_FILE_SEARCH_ENV
71 #define DtSYS_FILE_SEARCH_ENV          "DTHELPSEARCHPATH"
72 #endif
73
74 #ifndef DtUSER_FILE_SEARCH_ENV
75 #define DtUSER_FILE_SEARCH_ENV         "DTHELPUSERSEARCHPATH"
76 #endif
77
78 /*****************************************************************************
79  *      defines
80  *****************************************************************************/
81
82 #define VOLUME_EXT      ".hv"
83 #define FAMILY_EXT      ".hf"
84
85 /*****************************************************************************
86  *      static strings.
87  *****************************************************************************/
88 static  const char *ShellCmd    = "sh";
89 static  const char *UsageStr    =
90         "%s -dir <directory> [-generate] [-file <name>] [-lang <language>]\n";
91 static  const char *TopLocId    = "_hometopic";
92 static  const char *SlashString = "/";
93 static  const char *C_String    = "C";
94 static  const char *DefCharSet  = "C.ISO-8859-1";
95 static  const char *Family_ext  = FAMILY_EXT;
96 static  const char *Ext_Hv      = ".hv";
97 static  const char *Ext_Sdl     = ".sdl";
98
99 static  const char *SuperMsg =
100              "%s: Access denied for directory %s\nTry running as super user?\n";
101 static  const char *GeneralAccess =
102                         "%s: Unable to access %s - error status number  %d\n";
103 static  const char *NotDirectory = "%s: Element of %s is not a directory\n";
104 static  const char *WriteInvalid = "%s: Write to %s invalid\n";
105 static  const char *defaultTopic = "<TOPIC charset %s>\n";
106 static  const char *defaultTitle12 =
107         "<TYPE serif><WEIGHT bold><SIZE 12><ANGLE italic>\n%s\n</ANGLE></SIZE></WEIGHT></TYPE>\n";
108 static  const char *defaultTitle14 =
109         "<TITLE><TYPE serif><WEIGHT bold><SIZE 14>\n%s\n</SIZE></WEIGHT></TYPE></TITLE>\n";
110
111 static  const char *defaultTextBody =
112 "<ABBREV>Welcome to the Help Manager</ABBREV> \n\
113 <PARAGRAPH>Each of the titles listed below represents a <ANGLE italic> \n\
114 product family</> that has installed and registered its online help.  Each  \n\
115 title (and icon) is a hyperlink that lists the help within the family.</> \n\
116 <PARAGRAPH after 0 first 1 left 3 label \"<CHAR C.DT-SYMBOL-1><0xB7></>\">To \n\
117 display a list of the help available for a product family, choose its \n\
118 title (underlined text) or icon.</PARAGRAPH>  \n\
119 <PARAGRAPH after 0 first 1 left 3 label \"<CHAR C.DT-SYMBOL-1><0xB7></>\">\n\
120 Within a product  \n\
121 family, find the help you want to view, then choose its title.</PARAGRAPH> \n\
122 <PARAGRAPH first 1 left 3 label \"<CHAR C.DT-SYMBOL-1><0xB7></>\"> \n\
123 If you need help while using help windows, press F1.</PARAGRAPH>";
124
125 static  const char *defaultAlternate =
126 "<ABBREV>Welcome to the Help Manager</ABBREV> \n\
127 <LINK 0 \"Help4Help How-To-Register-Help\"> \n\
128 <TYPE serif><WEIGHT bold><SIZE 12><ANGLE italic> \n\
129 Note:\\ \\ \\ No Help Registered</SIZE></WEIGHT></TYPE></></LINK> \n\
130 <PARAGRAPH leftindent 3 firstindent 3> \n\
131 <WEIGHT bold>No product families have registered their online help \n\
132 files for browsing.</>  Help may be available for some applications by \n\
133 choosing Help commands directly within the applications.</>";
134
135 /*****************************************************************************
136  *      global variables.
137  *****************************************************************************/
138 char     *myName;
139 char     *Lang        = NULL;
140 char     *ParentName  = "_HOMETOPIC";
141
142 char    **TopicList   = NULL;
143
144 /* The family search list */
145 char    **FUserList    = NULL;
146 char    **FSysList     = NULL;
147
148 /* The volume search list */
149 char    **VUserList    = NULL;
150 char    **VSysList     = NULL;
151
152 char    **FamilyList     = NULL;        /* the names of the unique families */
153 char    **FullFamilyName = NULL;        /* the fully qualified family names */
154 char    **VolumeList     = NULL;        /* the names (only) of volume       */
155 char    **FullVolName    = NULL;        /* the fully qualified volume names */
156
157 char      TopicName [MAXPATHLEN + 2];
158
159 int       FamilyNum  = 0;
160
161 /* Global Message Catalog file names */
162 /*****************************************************************************
163  *      Private Function Declarations
164  *****************************************************************************/
165 extern  char    *FindFile (char *filename);
166
167 /*****************************************************************************
168  *      options and resources
169  *****************************************************************************/
170 typedef struct
171 {
172     char        *dir;
173     char        *file;
174     char        *lang;
175 } ApplicationArgs, *ApplicationArgsPtr;
176
177 static  ApplicationArgs App_args =
178   {
179         NULL,
180         "browser",
181         NULL,
182   };
183
184 /*****************************************************************************
185  * void MyExit(exit_val, pid)
186  *****************************************************************************/
187 void
188 MyExit (
189     int    exit_val,
190     pid_t  pid)
191 {
192   if (pid != ((pid_t) -1))
193       (void) kill(pid, SIGKILL);
194
195   exit (exit_val);
196 }
197
198 /*****************************************************************************
199  * char *GetMessage(set, n, s)
200  *****************************************************************************/
201 char *
202 GetMessage (
203     int    set,
204     int    n,
205     char  *s)
206 {
207    char *msg;
208    char *lang;
209    char  *catFileName=NULL;
210    static nl_catd nlmsg_fd;
211    static int first = 1;
212
213    if ( first ) 
214      {
215
216        /* Setup our default message catalog names if none have been set! */
217        /* Setup the short and long versions */
218 #ifdef __ultrix
219         catFileName = "dthelpgen.cat"; 
220 #else 
221         catFileName = "dthelpgen";
222 #endif
223         first = 0;
224
225         if (strcmp (Lang, "C") == 0) 
226           /*
227            * If LANG is not set or if LANG=C, then there
228            * is no need to open the message catalog - just
229            * return the built-in string "s".
230            */
231           nlmsg_fd = (nl_catd) -1;
232         else
233           nlmsg_fd = catopen(catFileName, NL_CAT_LOCALE);
234       }
235     msg=catgets(nlmsg_fd,set,n,s);
236     return (msg);
237
238 }
239
240 /*****************************************************************************
241  * Boolean *GetPath(filename)
242  *****************************************************************************/
243 Boolean
244 GetPath (char *filename, short strip, char ***list )
245 {
246     char  *ptr;
247     char **next = *list;
248
249     if (strip)
250       {
251         ptr = strrchr (filename, '/');
252         if (ptr)
253             *ptr = '\0';
254         else
255             filename = "./";
256       }
257
258     while (next != NULL && *next != NULL && strcmp (*next, filename))
259         next++;
260
261     if (next == NULL || *next == NULL)
262         *list = (char **) _DtHelpCeAddPtrToArray ((void **) (*list),
263                                                         strdup(filename));
264
265     return False;
266 }
267
268 /*****************************************************************************
269  * char *GetExtension(filename)
270  *     char *filename  - name of file to get the extension from.
271  * return  a pointer to the extension of the file name
272  *****************************************************************************/
273 char *
274 GetExtension(char *filename )
275 {
276     char *ext;
277
278 /*
279  * WARNING...
280  * need multi-byte functionality here
281  */
282     ext  = strrchr(filename, '.');
283     if (ext)
284         return(ext); /* safe because ext not in middle of character */
285
286   return(""); /* never returns NULL */
287 }
288
289 /*****************************************************************************
290  * Function:   CreateVolumeLink
291  *
292  *              outTopic        the output stream.
293  *              volume_name     Searches for a volume by this name.
294  *
295  * Reads a volume database and creates a label paragraph entry.
296  *
297  *****************************************************************************/
298 int
299 CreateVolumeLink (
300     CanvasHandle         canvas,
301     FILE                *outTopic,
302     char                *volume_name )
303 {
304     int          result = -1;
305     char        *title      = NULL;
306     char        *charSet    = (char *) DefCharSet;
307     char        *abstract   = NULL;
308     char        *filename   = NULL;
309     char        *pathName   = NULL;
310     VolumeHandle volume = NULL;
311
312     pathName = FindFile (volume_name);
313     if (pathName != NULL && _DtHelpCeOpenVolume(canvas,pathName,&volume) == 0)
314       {
315         if (_DtHelpCeGetVolumeTitle (canvas, volume, &title) == 0)
316             result = 0;
317         else if (_DtHelpCeGetTopicTitle(canvas,volume,(char*)TopLocId,&title)
318                                         == True)
319             result = 0;
320
321         if (result == 0)
322           {
323             if (_DtHelpCeGetAsciiVolumeAbstract(canvas,volume,&abstract) == -1)
324                 abstract = NULL;
325
326             charSet = _DtHelpCeGetVolumeLocale(volume);
327             if (charSet == NULL)
328                 charSet = (char *) DefCharSet;
329           }
330         _DtHelpCeCloseVolume (canvas, volume);
331       }
332
333     if (result == 0)
334       {
335         fprintf (outTopic, (GetMessage(3, 4, "<CHARACTERSET %s>\n")), charSet);
336         fprintf (outTopic,"<LINK 0 \"%s %s\">\n", volume_name, (char*)TopLocId);
337         fprintf (outTopic, (GetMessage(3, 5, (char*)defaultTitle12)), title);
338         fprintf (outTopic, "</LINK>\n");
339
340         /*
341          * put the abstract information about this
342          * family in the header file
343          */
344         fprintf (outTopic, "%s", GetMessage (3, 3, "<P before 1 first 1 left 1>\n"));
345
346         if (abstract != NULL)
347           {
348             fprintf (outTopic, (GetMessage (3, 4, "<CHARACTERSET %s>\n")),
349                                                                 charSet);
350             fprintf (outTopic, "%s\n", abstract);
351             fprintf (outTopic, "</CHARACTERSET>\n");
352             free (abstract);
353           }
354         fprintf (outTopic, "</P>\n</CHARACTERSET>\n");
355       }
356
357     if (charSet != DefCharSet)
358         free(charSet);
359
360     if (title)
361         free ((void *) title);
362     if (filename)
363         free ((void *) filename);
364
365     return result;
366 }
367
368 /*****************************************************************************
369  * Function:   CreateFamily
370  *
371  *****************************************************************************/
372 int
373 CreateFamily (
374     CanvasHandle canvas,
375     char    *family_name,
376     FILE    *out_volume,
377     FILE    *out_header,
378     FILE    *out_topic )
379 {
380     int          result = -1;
381     int          count = 0;
382     long         filepos;
383     char        *charSet = NULL;
384     char        *title = NULL;
385     char        *abstract = NULL;
386     char        *list = NULL;
387     char        *token = NULL;
388     char        *ptr;
389     char        *bitmap = NULL;
390     char         familyName [20];       /* FAMILY%d */
391     char         bitmapName [MAXPATHLEN + 2];
392     char         bitmapNameTemp [sizeof(bitmapName)];
393
394     XrmDatabase db;
395     char        *resType;
396     XrmValue    resValue;
397
398     db = XrmGetFileDatabase (family_name);
399     if (db)
400       {
401         /*
402          * get the title
403          */
404         if (XrmGetResource (db, "Family.Title", "family.title",
405                                                 &resType, &resValue))
406           {
407             title = (char *) resValue.addr;
408
409             /*
410              * get the abstract
411              */
412             if (XrmGetResource (db, "Family.Abstract", "family.abstract",
413                                                     &resType, &resValue))
414               {
415                 abstract = (char *) resValue.addr;
416
417                 /*
418                  * get the volumes list
419                  */
420                 if (XrmGetResource (db, "Family.Volumes", "family.volumes",
421                                                         &resType, &resValue))
422                   {
423                     list = (char *) resValue.addr;
424
425                     /*
426                      * get the character set
427                      */
428                     if (XrmGetResource (db, "Family.CharSet", "family.charSet",
429                                                     &resType, &resValue))
430                       {
431                         charSet = (char *) resValue.addr;
432
433                         /*
434                          * get the bitmap (optional)
435                          */
436                         if (XrmGetResource (db,
437                                         "Family.Bitmap", "family.bitmap",
438                                          &resType, &resValue))
439                             bitmap = (char *) resValue.addr;
440                       }
441                     else
442                       {
443                         fprintf (stderr,
444                                 (GetMessage (1, 14,
445                                     "%s: character set resource missing\n")),
446                                 family_name);
447                         return -1;
448                       }
449                   }
450                 else
451                   {
452                     fprintf (stderr,
453                                 (GetMessage (1, 13,
454                                         "%s: volumes resource missing\n")),
455                                 family_name);
456                     return -1;
457                   }
458               }
459             else
460               {
461                 fprintf (stderr,
462                         (GetMessage (1, 12, "%s: abstract resource missing\n")),
463                         family_name);
464                 return -1;
465               }
466           }
467         else
468           {
469             fprintf (stderr,
470                         (GetMessage (1, 11, "%s: title resource missing\n")),
471                         family_name);
472             return -1;
473           }
474         
475         if (title && abstract && list && charSet)
476           {
477             /*
478              * find out the position of the file pointer
479              */
480             filepos = ftell (out_topic);
481
482             /*
483              * write out the <TOPIC>
484              */
485             fprintf (out_topic, (GetMessage (3, 1, (char*)defaultTopic)),
486                                                         charSet, title);
487
488             /*
489              * write out the <TITLE>
490              */
491             fprintf (out_topic, (GetMessage (3, 2, (char*)defaultTitle14)),
492                                                                 title);
493             fprintf (out_topic, "%s", (GetMessage (3, 3, "<P before 1 first 1 left 1>\n")));
494             fprintf (out_topic, "%s\n", abstract);
495             fprintf (out_topic, "</P>\n");
496
497             do 
498               {
499                 token = NULL;
500                 list = _DtHelpCeGetNxtToken(list, &token);
501                 if (token && *token != '\0' && *token != '\n' &&
502                                 CreateVolumeLink (canvas,out_topic, token) == 0)
503                     count++;
504
505                 if (token)
506                 {
507                     free ((void *) token);
508                     token = NULL;
509                 }
510
511               } while (list && *list != '\0');
512
513             if (count)
514               {
515                 result = 0;
516                 sprintf (familyName, "FAMILY%d", FamilyNum);
517                 fprintf (out_topic, "</PARAGRAPH>\n</TOPIC>\n");
518
519                 /*
520                  * Put the link information in the header file
521                  */
522                 fprintf (out_header,
523                         (GetMessage (3, 4, "<CHARACTERSET %s>\n")), charSet);
524                 fprintf (out_header, "<LINK 0 %s>\n", familyName);
525                 fprintf (out_header, (GetMessage (3, 5, (char*)defaultTitle12)),
526                                                                 title);
527                 fprintf (out_header, "</LINK>\n");
528
529                 /*
530                  * put the abstract information about this
531                  * family in the header file
532                  */
533                 if (NULL != bitmap && *bitmap != '/')
534                   {
535                     snprintf(bitmapName, sizeof(bitmapName), "%s", family_name);
536                     ptr = strrchr (bitmapName, '/');
537                     if (ptr)
538                       {
539                         ptr++;
540                         *ptr = '\0';
541                         snprintf(bitmapNameTemp, sizeof(bitmapNameTemp), "%s%s", bitmapName, bitmap);
542                         strcpy(bitmapName, bitmapNameTemp);
543                         bitmap = bitmapName;
544                       }
545                     else
546                         bitmap = NULL;
547                   }
548
549                 if (NULL != bitmap)
550                   {
551                     fprintf (out_header,
552                         (GetMessage (3, 6,
553                                 "<P before 1 first 1 left 1 graphic %s glink %s gtypelink 0>\n")),
554                         bitmap, familyName);
555                   }
556                 else
557                     fprintf (out_header, "%s", GetMessage (3, 3, "<P before 1 first 1 left 1>\n"));
558                 fprintf (out_header, "%s\n", abstract);
559                 fprintf (out_header, "</P></CHARACTERSET>\n");
560
561                 /*
562                  * put the information in the volume file.
563                  */
564                 fprintf (out_volume, "*.%s.filepos: %ld\n",
565                                                 familyName, filepos);
566                 fprintf (out_volume, "*.%s.filename: %s\n",
567                                                 familyName, TopicName);
568                 TopicList = (char **) _DtHelpCeAddPtrToArray (
569                                                 (void **) TopicList,
570                                                 strdup (familyName));
571               }
572             else
573               {
574                 /*
575                  * rewind back to the original starting position
576                  */
577                 fseek (out_topic, filepos, 0);
578
579                 /*
580                  * didn't find any volumes for this family.
581                  */
582                 result = -2;
583               }
584           }
585         XrmDestroyDatabase (db);
586       }
587
588     return result;
589 }
590
591 /*****************************************************************************
592  * Function:   CheckFamilyList (name)
593  *
594  *  See if this family has been seen
595  *
596  *****************************************************************************/
597 int
598 CheckFamilyList (char    *name )
599 {
600     char **listPtr = FamilyList;
601
602     while (listPtr != NULL && *listPtr != NULL)
603       {
604         if (strcmp (*listPtr, name) == 0)
605             return True;
606         listPtr++;
607       }
608
609     return False;
610 }
611
612 /*****************************************************************************
613  * Function:   AddFamilyToList (name)
614  *
615  *  add the name to the family list
616  *
617  *****************************************************************************/
618 char **
619 AddFamilyToList (char    *name )
620 {
621
622     FamilyList = (char **) _DtHelpCeAddPtrToArray ((void **) FamilyList,
623                                                             strdup(name));
624     return FamilyList;
625 }
626
627 /*****************************************************************************
628  * Function:   ScanDirectory
629  *
630  *  scan a directory looking for family files.
631  *
632  *****************************************************************************/
633 void
634 ScanDirectory (
635     char    *directory,
636     long    *ret_time)
637 {
638     DIR    *pDir;
639     struct stat buf;
640
641     char    fullName [MAXPATHLEN + 2];
642     char   *ptr;
643     char   *ext;
644
645     struct dirent *pDirent;
646
647     *ret_time = 0;
648     if (stat(directory, &buf) == -1)
649         return;
650
651     *ret_time = buf.st_mtime;
652
653     pDir = opendir (directory);
654     if (pDir == NULL)
655         return;
656
657     snprintf(fullName, sizeof(fullName), "%s%s", directory, SlashString);
658     ptr = fullName + strlen (fullName);
659
660     /*
661      * skip over the "." and ".." entries.
662      */
663     (void) readdir (pDir);
664     (void) readdir (pDir);
665     pDirent = readdir (pDir);
666     while (pDirent)
667       {
668         ext = GetExtension (pDirent->d_name);
669         if (strcmp (ext, Family_ext) == 0)
670           {
671             if (CheckFamilyList (pDirent->d_name) == False)
672               {
673                 AddFamilyToList (pDirent->d_name);
674
675                 strcpy (ptr, pDirent->d_name);
676                 FullFamilyName = (char **) _DtHelpCeAddPtrToArray(
677                                         (void **)FullFamilyName,
678                                         strdup(fullName));
679               }
680           }
681         else if (strcmp(ext, Ext_Hv) == 0 || strcmp(ext, Ext_Sdl) == 0)
682           {
683             strcpy (ptr, pDirent->d_name);
684             VolumeList  = (char **) _DtHelpCeAddPtrToArray((void **)VolumeList,
685                                                 strdup(pDirent->d_name));
686             FullVolName = (char **) _DtHelpCeAddPtrToArray((void **)FullVolName,
687                                                 strdup(fullName));
688           }
689
690         pDirent = readdir (pDir);
691       }
692
693     closedir(pDir);
694     return;
695 }
696
697 /*****************************************************************************
698  * Function:   FindFile
699  *
700  *  Resolves the environment variable for all possible paths.
701  *
702  *****************************************************************************/
703 char *
704 FindFile (
705     char *filename)
706 {
707     int     i;
708     int     trimExt = 0;
709     int     different;
710     char   *fileExt;
711     char   *ext;
712     struct stat status;
713
714     fileExt = GetExtension(filename);
715     if (*fileExt == '\0')
716         trimExt = 1;
717
718     i = 0;
719     while (VolumeList != NULL && VolumeList[i] != NULL)
720       {
721         if (trimExt)
722           {
723             ext = GetExtension(VolumeList[i]);
724             *ext = '\0';
725           }
726
727         different = strcmp(filename, VolumeList[i]);
728         if (trimExt)
729            *ext = '.';
730
731         if (!different && access(FullVolName[i], R_OK) == 0
732                         && stat(FullVolName[i], &status) == 0
733                                 && S_ISDIR(status.st_mode) == 0)
734             return (FullVolName[i]);
735
736         i++;
737       }
738
739     return NULL;
740 }
741
742 /*****************************************************************************
743  * Function:   ExpandPaths
744  *
745  *  Resolves the environment variable for all possible paths.
746  *
747  *****************************************************************************/
748 void
749 ExpandPaths (
750     char   *lang,
751     char   *type,
752     char   *env_var,
753     char   *default_str,
754     char ***list)
755 {
756     short strip;
757     char *ptr;
758     char *hPtr;
759     char *src;
760     char *pathName;
761     char *searchPath;
762
763     searchPath = getenv (env_var);
764     if (searchPath == NULL || *searchPath == '\0')
765       {
766         if (default_str == NULL)
767             return;
768
769         searchPath = default_str;
770       }
771
772     searchPath = strdup (searchPath);
773
774     *list = NULL;
775     src   = searchPath;
776     do 
777       {
778         ptr = strchr (src, ':'); 
779         if (ptr)
780             *ptr = '\0';
781
782         /*
783          * check to see if %H is declared. If so, we're going
784          * to have to trim it before saving as the directory path.
785          */
786         strip = False;
787         hPtr  = strrchr (src, '%');
788         if (hPtr != NULL)
789           {
790             hPtr++;
791             if (*hPtr == 'H')
792                 strip = True;
793           }
794
795         /*
796          * check to see if the path needs expanding
797          */
798         if (NULL != strchr (src, '%'))
799             pathName = _DtHelpCeExpandPathname (src, NULL, type, NULL, lang,
800                                         (_DtSubstitutionRec *) NULL, 0);
801         else
802             pathName = strdup(src);
803
804         if (pathName)
805           {
806             GetPath (pathName, strip, list);
807             free (pathName);
808           }
809
810         if (ptr)
811           {
812             *ptr = ':';
813             ptr++;
814           }
815         src = ptr;
816       } while (src && *src);
817
818     free(searchPath);
819 }
820
821 /*****************************************************************************
822  * Function:   CheckTimeStamps
823  *
824  * Check the time stamps on the volume dir to determine if
825  * it needs regenerating.
826  *
827  *****************************************************************************/
828 int
829 CheckTimeStamps (
830     XrmDatabase   db,
831     char        **dir_list)
832 {
833     long         timeVal;
834     char        *value;
835     struct stat  buf;
836
837     while (*dir_list != NULL)
838       {
839         if (stat(*dir_list, &buf) == -1)
840             buf.st_mtime = 0;
841
842         value = _DtHelpCeGetResourceString(db, *dir_list,
843                                                 "TimeStamp", "timeStamp");
844         timeVal = atol(value);
845         if (timeVal != buf.st_mtime)
846             return 1;
847
848         dir_list++;
849       }
850
851     return 0;
852 }
853
854 /*****************************************************************************
855  * Function:   CheckInfo
856  *
857  *  Check the information in the volume to determine if it needs regenerating.
858  *
859  *****************************************************************************/
860 int
861 CheckInfo (
862     char *file)
863 {
864     int         result = 1;
865     char        **list1, **list2;
866     char        **volDirList;
867     XrmDatabase db;
868
869     db = XrmGetFileDatabase (file);
870     if (db != NULL)
871       {
872         volDirList = _DtHelpCeGetResourceStringArray(db, NULL,
873                                                         "DirList", "dirList");
874         if (volDirList != NULL)
875           {
876             result = 0;
877             list1  = volDirList;
878             list2  = FUserList;
879             while (result == 0 && *list1 != NULL
880                                         && list2 != NULL && *list2 != NULL)
881               {
882                 result = strcmp(*list1, *list2);
883                 list1++;
884                 list2++;
885               }
886
887             if (list2 != NULL && *list2 != NULL)
888                 result = 1;
889
890             list2  = FSysList;
891             while (result == 0 && *list1 != NULL
892                                         && list2 != NULL && *list2 != NULL)
893               {
894                 result = strcmp(*list1, *list2);
895                 list1++;
896                 list2++;
897               }
898
899             if (list2 != NULL && *list2 != NULL)
900                 result = 1;
901
902             list2  = VUserList;
903             while (result == 0 && *list1 != NULL
904                                         && list2 != NULL && *list2 != NULL)
905               {
906                 result = strcmp(*list1, *list2);
907                 list1++;
908                 list2++;
909               }
910
911             if (list2 != NULL && *list2 != NULL)
912                 result = 1;
913
914             list2  = VSysList;
915             while (result == 0 && *list1 != NULL
916                                         && list2 != NULL && *list2 != NULL)
917               {
918                 result = strcmp(*list1, *list2);
919                 list1++;
920                 list2++;
921               }
922
923             if (*list1 != NULL || (list2 != NULL && *list2 != NULL))
924                 result = 1;
925
926             if (result == 0)
927                 result = CheckTimeStamps(db, volDirList);
928
929             _DtHelpCeFreeStringArray(volDirList);
930           }
931         XrmDestroyDatabase(db);
932       }
933
934     return result;
935 }
936
937 /***************************************************************************** 
938  *                      Main routine
939  *****************************************************************************/
940 int
941 main(
942     int    argc,
943     char  *argv[] )
944 {
945     int      i;
946     int      result;
947     int      foundFamily;
948     int      foundVolumes;
949     int      usedUser = 0;
950     int      doGen    = 0;
951
952     char     tmpVolume  [MAXPATHLEN + 2];
953     char     tmpVolumeTemp[sizeof(tmpVolume)];
954     char     tmpVolume2 [MAXPATHLEN + 2];
955     char     tmpTopic   [MAXPATHLEN + 2];
956     char     tmpHeader  [MAXPATHLEN + 2];
957     char     headerName [MAXPATHLEN + 2];
958     char     baseName   [MAXPATHLEN + 2];
959     char     baseNameTemp[sizeof(baseName)];
960     char     tempName   [MAXPATHLEN + 2];
961     char   **next;
962     char    *charSet;
963     char    *topicTitle;
964     char    *ptr;
965     char    *endDir;
966
967     long    preamble;
968     long    modTime;
969     FILE   *outVolume;
970     FILE   *outTopic;
971     FILE   *outHeader;
972     pid_t   childPid = (pid_t) -1;
973     CanvasHandle canvasHandle;
974
975     myName = strrchr (argv[0], '/');
976     if (myName)
977         myName++;
978     else
979         myName = argv[0];
980
981    /*
982     * have to do a setlocale here, so that the usage message is in the
983     * correct language.
984     */
985    Lang = getenv ("LANG");
986
987    /*
988     * use the default if no lang is specified.
989     */
990    if (Lang == NULL || *Lang == '\0')
991         Lang = (char*)C_String;
992
993     setlocale(LC_ALL, "");
994     _DtEnvControl(DT_ENV_SET);
995
996     /*
997      * now process the arguments
998      */
999     for (i = 1; i < argc; i++)
1000       {
1001         if (argv[i][0] == '-')
1002           {
1003             if (argv[i][1] == 'd' && i + 1 < argc)
1004                 App_args.dir = argv[++i];
1005             else if (argv[i][1] == 'f' && i + 1 < argc)
1006                 App_args.file = argv[++i];
1007             else if (argv[i][1] == 'g')
1008                 doGen = 1;
1009             else if (argv[i][1] == 'l' && i + 1 < argc)
1010                 App_args.lang = argv[++i];
1011             else
1012               {
1013                 fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1014                 exit (1);
1015               }
1016           }
1017         else
1018           {
1019             fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1020             exit (1);
1021           }
1022       }
1023
1024     /*
1025      * get the language we are working with.
1026      */
1027     if (App_args.lang != NULL)
1028       {
1029         /*
1030          * Set the locale! Since the user has specified a (likely)
1031          * different language to do the processing in, we need to
1032          * do a setlocale to work with the new language.
1033          */
1034         Lang = App_args.lang;
1035         if (setlocale(LC_ALL, Lang) == NULL)
1036           {
1037             fprintf (stderr, (GetMessage(1, 20,
1038                         "%s: Invalid system language specified - %s\n")),
1039                                 myName, Lang);
1040             exit (1);
1041           }
1042         _DtEnvControl(DT_ENV_SET);
1043       }
1044     Lang = strdup(Lang);
1045
1046     /*
1047      * get the directory to work in
1048      */
1049     if (NULL == App_args.dir)
1050       {
1051         fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1052         exit (1);
1053       }
1054
1055     if (App_args.dir[0] != '/')
1056       {
1057         if (getcwd (baseName, MAXPATHLEN) == NULL)
1058           {
1059             fprintf (stderr, (GetMessage (1, 18,
1060  "%s: Unable to access current working directory - error status number %d\n")),
1061                                 myName, errno);
1062             exit (1);
1063           }
1064         snprintf(baseNameTemp, sizeof(baseNameTemp), "%s/%s", baseName, App_args.dir);
1065         strcpy(baseName, baseNameTemp);
1066       }
1067     else
1068         snprintf(baseName, sizeof(baseName), "%s", App_args.dir);
1069
1070     /*
1071      * make sure the directory exists
1072      */
1073     ptr = _DtHelpCeExpandPathname (baseName, NULL, "help", NULL, Lang,
1074                                         (_DtSubstitutionRec *) NULL, 0);
1075     if (ptr == NULL || *ptr == '\0')
1076       {
1077         fprintf (stderr,
1078                 (GetMessage (1, 15, "%s: Destination directory missing\n")),
1079                         myName);
1080         exit (1);
1081       }
1082
1083     snprintf(tmpVolume, sizeof(tmpVolume), "%s", ptr);
1084     if (tmpVolume[strlen (tmpVolume) - 1] != '/') {
1085         snprintf(tmpVolumeTemp, sizeof(tmpVolumeTemp), "%s%s", tmpVolume, SlashString);
1086         strcpy(tmpVolume, tmpVolumeTemp);
1087     }
1088
1089     free (ptr);
1090
1091     /*
1092      * march down the path, checking that
1093      *          1) it exists
1094      *          2) the caller has access permission.
1095      *          3) resolve all symbolic links
1096      */
1097     endDir = strchr (tmpVolume, '/');
1098     if (endDir != NULL)
1099       {
1100         endDir++;
1101         endDir = strchr (endDir, '/');
1102       }
1103     while (endDir && *endDir != '\0')
1104       {
1105         /*
1106          * remember the rest of the string (including the slash)
1107          * and strip the trailing slash from the directory path.
1108          */
1109         snprintf(tmpVolume2, sizeof(tmpVolume2), "%s", endDir);
1110         *endDir = '\0';
1111
1112         /*
1113          * trace the path and copy the new string into the old buffer.
1114          */
1115         ptr = _DtHelpCeTracePathName(tmpVolume);
1116         if (ptr != NULL)
1117           {
1118             snprintf(tmpVolume, sizeof(tmpVolume), "%s", ptr);
1119             free (ptr);
1120           }
1121
1122         if (access (tmpVolume, F_OK) == -1)
1123           {
1124             switch (errno)
1125               {
1126                 case ENOTDIR:
1127                         ptr = GetMessage (1, 2, (char*)NotDirectory);
1128                         fprintf (stderr, ptr, myName, tmpVolume);
1129                         exit (1);
1130
1131                 case EACCES:
1132                         ptr = GetMessage (1, 3, (char*)SuperMsg);
1133                         fprintf (stderr, ptr, myName, tmpVolume);
1134                         exit (1);
1135
1136                 case ENOENT:
1137                         if (mkdir(tmpVolume,
1138                             (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) == -1
1139                                 && errno != EEXIST && errno != EROFS)
1140                           {
1141                             switch (errno)
1142                               {
1143                                 case ENOTDIR:
1144                                         ptr = GetMessage(1,2,
1145                                                         (char*)NotDirectory);
1146                                         break;
1147
1148                                 case EACCES:
1149                                         ptr = GetMessage(1, 3, 
1150                                                         (char*)SuperMsg);
1151                                         break;
1152
1153                                 case ENOENT:
1154                                         ptr = GetMessage(1, 4,
1155                                     "%s: Element of %s does not exist\n");
1156                                         break;
1157
1158                                 case ENOSPC:
1159                                         ptr = GetMessage (1, 5,
1160                                 "%s: File system containing %s is full\n");
1161                                         break;
1162
1163                                 default:
1164                                         ptr = GetMessage(1,6,
1165                                                         (char*)GeneralAccess);
1166                                         break;
1167                               }
1168                             fprintf (stderr, ptr, myName, tmpVolume, errno);
1169                             exit (1);
1170                           }
1171                         break;
1172                 default:
1173                         ptr = GetMessage (1, 6, (char*)GeneralAccess);
1174                         fprintf (stderr, ptr, myName, tmpVolume, errno);
1175                         exit (1);
1176               }
1177           }
1178
1179         /*
1180          * point to the end of the string (past where the slash will go)
1181          */
1182         endDir = tmpVolume + strlen(tmpVolume) + 2;
1183
1184         /*
1185          * append the rest of the directory spec that hasn't been checked.
1186          */
1187         strcat (tmpVolume, tmpVolume2);
1188         endDir = strchr (endDir, '/');
1189       }
1190
1191
1192     /*
1193      * get temporary files for the volume and topic file.
1194      */
1195     snprintf(tmpVolumeTemp, sizeof(tmpVolumeTemp), "%s%s", tmpVolume, App_args.file);
1196     strcpy(tmpVolume, tmpVolumeTemp);
1197
1198     (void) strcpy (tmpHeader, tmpVolume);
1199     (void) strcpy (tmpTopic, tmpVolume);
1200
1201     snprintf(tmpVolumeTemp, sizeof(tmpVolumeTemp), "%s%s", tmpVolume, Ext_Hv);
1202     strcpy(tmpVolume, tmpVolumeTemp);
1203     (void) strcat (tmpHeader, "00.ht");
1204     (void) strcat (tmpTopic , "01.ht");
1205
1206     result = access (tmpVolume, F_OK);
1207
1208     /*
1209      * If it exists, make sure the invoker can write to it.
1210      */
1211     if (result == 0)
1212       {
1213         if (access (tmpVolume, W_OK) == -1)
1214           {
1215             if (errno == EROFS)
1216                 ptr = GetMessage (1, 7,
1217                         "%s: File system containing %s is read only\n");
1218             else if (errno == EACCES)
1219                 ptr = GetMessage (1, 8,
1220                         "%s: Requires root permission to write to %s\n");
1221             else
1222                 ptr = GetMessage (1, 9, (char*)WriteInvalid);
1223
1224             fprintf (stderr, ptr, myName, tmpVolume, errno);
1225             exit (1);
1226           }
1227       }
1228     else if (result == -1 && errno != ENOENT)
1229       {
1230         ptr = GetMessage (1, 6, (char*)GeneralAccess);
1231         fprintf (stderr, ptr, myName, tmpVolume, errno);
1232         exit (1);
1233       }
1234     else /* file does not exist */
1235         doGen = 1;
1236
1237
1238     /*
1239      * Find out if we have any paths to check.
1240      */
1241     ExpandPaths(Lang, "families", DtUSER_FILE_SEARCH_ENV, NULL, &FUserList);
1242     ExpandPaths(Lang, "volumes", DtUSER_FILE_SEARCH_ENV, NULL, &VUserList);
1243     ExpandPaths(Lang, "families", DtSYS_FILE_SEARCH_ENV ,
1244                                         DtDEFAULT_SYSTEM_PATH, &FSysList);
1245     ExpandPaths(Lang, "volumes", DtSYS_FILE_SEARCH_ENV ,
1246                                         DtDEFAULT_SYSTEM_PATH, &VSysList);
1247     if (((FUserList == NULL || *FUserList == NULL) &&
1248         (FSysList  == NULL || *FSysList  == NULL)) ||
1249         ((VUserList == NULL || *VUserList == NULL) &&
1250         (VSysList  == NULL || *VSysList  == NULL)))
1251       {
1252         ptr = GetMessage (1, 10, "%s: Search Path empty\n");
1253         fprintf (stderr, ptr, myName);
1254         exit (1);
1255       }
1256
1257     /*
1258      * If we already haven't determined that the volume needs (re)generating
1259      * check the info squirreled away in the old volume.
1260      */
1261     if (doGen == 0)
1262         doGen = CheckInfo(tmpVolume);
1263
1264     /*
1265      * the volume doesn't need (re)generating.
1266      * exit now.
1267      */
1268     if (doGen == 0)
1269         exit(0);
1270
1271     /*
1272      * create a canvas for the functions.
1273      */
1274     canvasHandle = _DtHelpCeCreateDefCanvas();
1275     if (canvasHandle == NULL)
1276       {
1277         fprintf (stderr, GetMessage(1, 19,"%s: Unable to allocate memory\n"),
1278                                                                 myName);
1279         MyExit (1, -1);
1280       }
1281
1282     /*
1283      * open the individual files that will hold the browser information.
1284      * <file>.hv
1285      */
1286     outVolume = fopen (tmpVolume, "w");
1287     if (outVolume == NULL)
1288       {
1289         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1290         fprintf (stderr, ptr, myName, tmpVolume, errno);
1291         _DtHelpCeDestroyCanvas(canvasHandle);
1292         MyExit (1, -1);
1293       }
1294
1295     /*
1296      * <file>00.ht
1297      */
1298     outTopic = fopen (tmpTopic, "w");
1299     if (outTopic == NULL)
1300       {
1301         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1302         fprintf (stderr, ptr, myName, tmpTopic, errno);
1303         (void) unlink (tmpVolume);
1304         _DtHelpCeDestroyCanvas(canvasHandle);
1305         MyExit (1, -1);
1306       }
1307
1308     /*
1309      * <file>01.ht
1310      */
1311     outHeader = fopen (tmpHeader, "w");
1312     if (outHeader == NULL)
1313       {
1314         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1315         fprintf (stderr, ptr, myName, tmpHeader, errno);
1316         (void) unlink (tmpVolume);
1317         (void) unlink (tmpTopic);
1318         _DtHelpCeDestroyCanvas(canvasHandle);
1319         MyExit (1, -1);
1320       }
1321
1322     /*
1323      * fork off the dtksh script that will put up a dialog
1324      * telling the user that we're building help browser
1325      * information.
1326      */
1327 #ifdef  __hpux
1328     childPid = vfork();
1329 #else
1330     childPid = fork();
1331 #endif
1332     /*
1333      * if this is the child, exec the dthelpgen.ds script.
1334      */
1335     if (childPid == 0)
1336       {
1337         execlp("dthelpgen.ds", "dthelpgen.ds",
1338                                 ((char *) 0), ((char *) 0), ((char *) 0));
1339         _exit(1);
1340       }
1341
1342     /*
1343      * initialize the main topic
1344      */
1345     strcpy (TopicName, App_args.file);
1346     strcat (TopicName, "01.ht");
1347
1348     strcpy (headerName, App_args.file);
1349     strcat (headerName, "00.ht");
1350
1351     /*
1352      * The original dthelpgen extracts the CDE Standard name from
1353      * its message catalogue( set 2/msg 1 ).
1354      * But on IBM ODE, this is a problem. For example,
1355      * fr_FR's dthelpgen.cat has 
1356      *    fr_FR.ISO-8859-1 in set 2/msg 1.
1357      * Correct Fr_FR's message catalogue must have,
1358      *    fr_FR.IBM-850
1359      * there. But current IBM ode's Makefile cannot do this. Instead put
1360      *    fr_FR.ISO-8859-1. ( That is "do nothing" ).
1361      * To fix this, dthelpgen converts the current IBM LANG to CDE
1362      * standard name with _DtLcx*() function provided by libDtHelp.a as
1363      * internal API.
1364      */
1365 #ifdef _AIX
1366     {
1367         _DtXlateDb db = NULL;
1368         int  ret;
1369         char plat[_DtPLATFORM_MAX_LEN];
1370         int  execver;
1371         int  compver;
1372         char *ret_stdLocale;
1373         char *ret_stdLangTerr;
1374         char *ret_stdCodeset;
1375         char *ret_stdModifier;
1376
1377         ret = _DtLcxOpenAllDbs( &db );
1378         if ( !ret ) {
1379             ret = _DtXlateGetXlateEnv( db, plat, &execver, &compver );
1380             if ( !ret ) {
1381                 ret =  _DtLcxXlateOpToStd( db, plat, execver,
1382                                         DtLCX_OPER_SETLOCALE,
1383                                         setlocale( LC_MESSAGES, NULL ),
1384                                         &ret_stdLocale, &ret_stdLangTerr,
1385                                         &ret_stdCodeset, &ret_stdModifier );
1386                 if ( !ret ) {
1387                     charSet = strdup( ret_stdLocale );
1388                 } else {
1389                     charSet = "C.ISO-8859-1";
1390                 }
1391             } else {
1392                 charSet = "C.ISO-8859-1";
1393             }
1394             ret = _DtLcxCloseDb( &db );
1395         } else {
1396             charSet = "C.ISO-8859-1";
1397         }
1398
1399     }
1400 #else /* _AIX */
1401     charSet    = strdup (GetMessage (2, 1, "C.ISO-8859-1"));
1402 #endif /* _AIX */
1403     topicTitle = strdup (GetMessage (2, 2, "Welcome to Help Manager"));
1404
1405     fprintf (outHeader, (GetMessage (3, 1, (char*)defaultTopic)),
1406                                                 charSet, topicTitle);
1407
1408     fprintf (outHeader, (GetMessage (3, 2, (char*)defaultTitle14)), topicTitle);
1409     free(topicTitle);
1410
1411     preamble = ftell (outHeader);
1412     fprintf (outHeader, "%s\n", GetMessage (2, 3, (char*)defaultTextBody));
1413
1414     /*
1415      * loop through the directories looking for all the unique families
1416      * and -all- the volumes.
1417      */
1418     fprintf (outVolume, "!# Last modification time stamp per directory\n");
1419     next = FUserList;
1420     while (next != NULL && *next != NULL)
1421       {
1422         ScanDirectory(*next, &modTime);
1423         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1424         next++;
1425       }
1426     next = FSysList;
1427     while (next != NULL && *next != NULL)
1428       {
1429         ScanDirectory(*next, &modTime);
1430         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1431         next++;
1432       }
1433
1434     next = VUserList;
1435     while (next != NULL && *next != NULL)
1436       {
1437         ScanDirectory(*next, &modTime);
1438         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1439         next++;
1440       }
1441     next = VSysList;
1442     while (next != NULL && *next != NULL)
1443       {
1444         ScanDirectory(*next, &modTime);
1445         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1446         next++;
1447       }
1448
1449     fprintf (outVolume, "*.charSet:     %s\n", charSet);
1450     free(charSet);
1451     fprintf (outVolume, "\n!# Topic filenames and offsets\n");
1452
1453     /*
1454      * Now create families.
1455      */
1456     foundVolumes = 0;
1457     foundFamily  = 0;
1458     for (next = FullFamilyName; next != NULL && *next != NULL; next++)
1459       {
1460         result = CreateFamily(canvasHandle,*next,outVolume,outHeader,outTopic);
1461         if (result == 0)
1462           {
1463             FamilyNum++;
1464             foundVolumes = 1;
1465             foundFamily  = 1;
1466           }
1467         else if (result == -2)
1468             foundFamily  = 1;
1469       }
1470
1471     if (foundFamily == 0)
1472         fprintf(stderr,
1473                 GetMessage(1, 16, "%s: Zero Family files found\n"), myName);
1474     else if (foundVolumes == 0)
1475         fprintf (stderr,
1476                 GetMessage (1, 17, "%s: Zero Volume files found\n"), myName);
1477
1478     /*
1479      * Clean up
1480      */
1481     if (FamilyList != NULL)
1482         _DtHelpCeFreeStringArray(FamilyList);
1483     if (FullFamilyName != NULL)
1484         _DtHelpCeFreeStringArray(FullFamilyName);
1485     if (VolumeList != NULL)
1486         _DtHelpCeFreeStringArray(VolumeList);
1487     if (FullVolName != NULL)
1488         _DtHelpCeFreeStringArray(FullVolName);
1489
1490     /*
1491      * If no family or volume files were found,
1492      * write out the alternative preamble
1493      */
1494     if (foundFamily == 0 || foundVolumes == 0)
1495       {
1496         fseek (outHeader, preamble, 0);
1497         fprintf (outHeader, "%s\n", GetMessage (2, 5, (char*)defaultAlternate));
1498       }
1499
1500     /*
1501      * write the ending message and finish the topic.
1502      */
1503     fprintf (outHeader, "</TOPIC>\n");
1504
1505     /*
1506      * write out the volume resouces
1507      */
1508     fprintf (outVolume, "\n\n!# Top (or home) topic filename and offset\n");
1509     fprintf (outVolume, "*.%s.filepos:   0\n" , ParentName);
1510     fprintf (outVolume, "*.%s.filename:  %s\n", ParentName, headerName);
1511
1512     fprintf (outVolume, "\n\n!# Volume Title\n");
1513     fprintf (outVolume, "*.title:     %s\n",
1514                       GetMessage (2, 4, "Help - Top Level"));
1515
1516     /*
1517      * top topic
1518      */
1519     fprintf (outVolume, "\n\n!# Topic Home Location\n");
1520     fprintf (outVolume, "*.topTopic:  %s\n", ParentName);
1521
1522     /*
1523      * topic heirarchy
1524      */
1525     fprintf (outVolume, "\n\n!# Topic Heirarchy\n");
1526     fprintf (outVolume, "*.%s.parent:  \n", ParentName);
1527     for (next = TopicList; next && *next; next++)
1528         fprintf (outVolume, "*.%s.parent: %s\n", *next, ParentName);
1529
1530     /*
1531      * topic list
1532      */
1533     fprintf (outVolume, "\n\n!# Topic List\n");
1534     fprintf (outVolume, "*.topicList: %s", ParentName);
1535     next = TopicList;
1536     while (next && *next)
1537       {
1538         fprintf (outVolume, " \\\n      %s", *next);
1539         next++;
1540       }
1541     fprintf (outVolume, "\n");
1542
1543     /*
1544      * The paths used to create this information.
1545      */
1546     fprintf (outVolume, "\n\n!# Paths Searched\n");
1547     fprintf (outVolume, "*.dirList: ");
1548
1549     next = FUserList;
1550     while (next != NULL && *next != NULL)
1551       {
1552         fprintf (outVolume, " \\\n      %s", *next);
1553         next++;
1554       }
1555     next = FSysList;
1556     while (next != NULL && *next != NULL)
1557       {
1558         fprintf (outVolume, " \\\n      %s", *next);
1559         next++;
1560       }
1561     next = VUserList;
1562     while (next != NULL && *next != NULL)
1563       {
1564         fprintf (outVolume, " \\\n      %s", *next);
1565         next++;
1566       }
1567     next = VSysList;
1568     while (next != NULL && *next != NULL)
1569       {
1570         fprintf (outVolume, " \\\n      %s", *next);
1571         next++;
1572       }
1573     fprintf (outVolume, "\n");
1574
1575     /*
1576      * close the volumes.
1577      */
1578     fclose (outVolume);
1579     fclose (outHeader);
1580     fclose (outTopic);
1581
1582     if (TopicList != NULL)
1583         _DtHelpCeFreeStringArray(TopicList);
1584
1585     if (FUserList != NULL)
1586         _DtHelpCeFreeStringArray(FUserList);
1587     if (FSysList != NULL)
1588         _DtHelpCeFreeStringArray(FSysList);
1589     if (VUserList != NULL)
1590         _DtHelpCeFreeStringArray(VUserList);
1591     if (VSysList != NULL)
1592         _DtHelpCeFreeStringArray(VSysList);
1593
1594     _DtHelpCeDestroyCanvas(canvasHandle);
1595
1596     MyExit (0, childPid);
1597 }