Spelling fixes
[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 libraries 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        *pathName   = NULL;
309     VolumeHandle volume = NULL;
310
311     pathName = FindFile (volume_name);
312     if (pathName != NULL && _DtHelpCeOpenVolume(canvas,pathName,&volume) == 0)
313       {
314         if (_DtHelpCeGetVolumeTitle (canvas, volume, &title) == 0)
315             result = 0;
316         else if (_DtHelpCeGetTopicTitle(canvas,volume,(char*)TopLocId,&title)
317                                         == True)
318             result = 0;
319
320         if (result == 0)
321           {
322             if (_DtHelpCeGetAsciiVolumeAbstract(canvas,volume,&abstract) == -1)
323                 abstract = NULL;
324
325             charSet = _DtHelpCeGetVolumeLocale(volume);
326             if (charSet == NULL)
327                 charSet = (char *) DefCharSet;
328           }
329         _DtHelpCeCloseVolume (canvas, volume);
330       }
331
332     if (result == 0)
333       {
334         fprintf (outTopic, (GetMessage(3, 4, "<CHARACTERSET %s>\n")), charSet);
335         fprintf (outTopic,"<LINK 0 \"%s %s\">\n", volume_name, (char*)TopLocId);
336         fprintf (outTopic, (GetMessage(3, 5, (char*)defaultTitle12)), title);
337         fprintf (outTopic, "</LINK>\n");
338
339         /*
340          * put the abstract information about this
341          * family in the header file
342          */
343         fprintf (outTopic, "%s", GetMessage (3, 3, "<P before 1 first 1 left 1>\n"));
344
345         if (abstract != NULL)
346           {
347             fprintf (outTopic, (GetMessage (3, 4, "<CHARACTERSET %s>\n")),
348                                                                 charSet);
349             fprintf (outTopic, "%s\n", abstract);
350             fprintf (outTopic, "</CHARACTERSET>\n");
351             free (abstract);
352           }
353         fprintf (outTopic, "</P>\n</CHARACTERSET>\n");
354       }
355
356     if (charSet != DefCharSet)
357         free(charSet);
358
359     if (title)
360         free ((void *) title);
361
362     return result;
363 }
364
365 /*****************************************************************************
366  * Function:   CreateFamily
367  *
368  *****************************************************************************/
369 int
370 CreateFamily (
371     CanvasHandle canvas,
372     char    *family_name,
373     FILE    *out_volume,
374     FILE    *out_header,
375     FILE    *out_topic )
376 {
377     int          result = -1;
378     int          count = 0;
379     long         filepos;
380     char        *charSet = NULL;
381     char        *title = NULL;
382     char        *abstract = NULL;
383     char        *list = NULL;
384     char        *token = NULL;
385     char        *ptr;
386     char        *bitmap = NULL;
387     char         familyName [20];       /* FAMILY%d */
388     char         bitmapName [MAXPATHLEN + 2];
389     char         bitmapNameTemp [sizeof(bitmapName)];
390
391     XrmDatabase db;
392     char        *resType;
393     XrmValue    resValue;
394
395     db = XrmGetFileDatabase (family_name);
396     if (db)
397       {
398         /*
399          * get the title
400          */
401         if (XrmGetResource (db, "Family.Title", "family.title",
402                                                 &resType, &resValue))
403           {
404             title = (char *) resValue.addr;
405
406             /*
407              * get the abstract
408              */
409             if (XrmGetResource (db, "Family.Abstract", "family.abstract",
410                                                     &resType, &resValue))
411               {
412                 abstract = (char *) resValue.addr;
413
414                 /*
415                  * get the volumes list
416                  */
417                 if (XrmGetResource (db, "Family.Volumes", "family.volumes",
418                                                         &resType, &resValue))
419                   {
420                     list = (char *) resValue.addr;
421
422                     /*
423                      * get the character set
424                      */
425                     if (XrmGetResource (db, "Family.CharSet", "family.charSet",
426                                                     &resType, &resValue))
427                       {
428                         charSet = (char *) resValue.addr;
429
430                         /*
431                          * get the bitmap (optional)
432                          */
433                         if (XrmGetResource (db,
434                                         "Family.Bitmap", "family.bitmap",
435                                          &resType, &resValue))
436                             bitmap = (char *) resValue.addr;
437                       }
438                     else
439                       {
440                         fprintf (stderr,
441                                 (GetMessage (1, 14,
442                                     "%s: character set resource missing\n")),
443                                 family_name);
444                         return -1;
445                       }
446                   }
447                 else
448                   {
449                     fprintf (stderr,
450                                 (GetMessage (1, 13,
451                                         "%s: volumes resource missing\n")),
452                                 family_name);
453                     return -1;
454                   }
455               }
456             else
457               {
458                 fprintf (stderr,
459                         (GetMessage (1, 12, "%s: abstract resource missing\n")),
460                         family_name);
461                 return -1;
462               }
463           }
464         else
465           {
466             fprintf (stderr,
467                         (GetMessage (1, 11, "%s: title resource missing\n")),
468                         family_name);
469             return -1;
470           }
471         
472         if (title && abstract && list && charSet)
473           {
474             /*
475              * find out the position of the file pointer
476              */
477             filepos = ftell (out_topic);
478
479             /*
480              * write out the <TOPIC>
481              */
482             fprintf (out_topic, (GetMessage (3, 1, (char*)defaultTopic)),
483                                                         charSet, title);
484
485             /*
486              * write out the <TITLE>
487              */
488             fprintf (out_topic, (GetMessage (3, 2, (char*)defaultTitle14)),
489                                                                 title);
490             fprintf (out_topic, "%s", (GetMessage (3, 3, "<P before 1 first 1 left 1>\n")));
491             fprintf (out_topic, "%s\n", abstract);
492             fprintf (out_topic, "</P>\n");
493
494             do 
495               {
496                 token = NULL;
497                 list = _DtHelpCeGetNxtToken(list, &token);
498                 if (token && *token != '\0' && *token != '\n' &&
499                                 CreateVolumeLink (canvas,out_topic, token) == 0)
500                     count++;
501
502                 if (token)
503                 {
504                     free ((void *) token);
505                     token = NULL;
506                 }
507
508               } while (list && *list != '\0');
509
510             if (count)
511               {
512                 result = 0;
513                 sprintf (familyName, "FAMILY%d", FamilyNum);
514                 fprintf (out_topic, "</PARAGRAPH>\n</TOPIC>\n");
515
516                 /*
517                  * Put the link information in the header file
518                  */
519                 fprintf (out_header,
520                         (GetMessage (3, 4, "<CHARACTERSET %s>\n")), charSet);
521                 fprintf (out_header, "<LINK 0 %s>\n", familyName);
522                 fprintf (out_header, (GetMessage (3, 5, (char*)defaultTitle12)),
523                                                                 title);
524                 fprintf (out_header, "</LINK>\n");
525
526                 /*
527                  * put the abstract information about this
528                  * family in the header file
529                  */
530                 if (NULL != bitmap && *bitmap != '/')
531                   {
532                     snprintf(bitmapName, sizeof(bitmapName), "%s", family_name);
533                     ptr = strrchr (bitmapName, '/');
534                     if (ptr)
535                       {
536                         ptr++;
537                         *ptr = '\0';
538                         snprintf(bitmapNameTemp, sizeof(bitmapNameTemp), "%s%s", bitmapName, bitmap);
539                         strcpy(bitmapName, bitmapNameTemp);
540                         bitmap = bitmapName;
541                       }
542                     else
543                         bitmap = NULL;
544                   }
545
546                 if (NULL != bitmap)
547                   {
548                     fprintf (out_header,
549                         (GetMessage (3, 6,
550                                 "<P before 1 first 1 left 1 graphic %s glink %s gtypelink 0>\n")),
551                         bitmap, familyName);
552                   }
553                 else
554                     fprintf (out_header, "%s", GetMessage (3, 3, "<P before 1 first 1 left 1>\n"));
555                 fprintf (out_header, "%s\n", abstract);
556                 fprintf (out_header, "</P></CHARACTERSET>\n");
557
558                 /*
559                  * put the information in the volume file.
560                  */
561                 fprintf (out_volume, "*.%s.filepos: %ld\n",
562                                                 familyName, filepos);
563                 fprintf (out_volume, "*.%s.filename: %s\n",
564                                                 familyName, TopicName);
565                 TopicList = (char **) _DtHelpCeAddPtrToArray (
566                                                 (void **) TopicList,
567                                                 strdup (familyName));
568               }
569             else
570               {
571                 /*
572                  * rewind back to the original starting position
573                  */
574                 fseek (out_topic, filepos, 0);
575
576                 /*
577                  * didn't find any volumes for this family.
578                  */
579                 result = -2;
580               }
581           }
582         XrmDestroyDatabase (db);
583       }
584
585     return result;
586 }
587
588 /*****************************************************************************
589  * Function:   CheckFamilyList (name)
590  *
591  *  See if this family has been seen
592  *
593  *****************************************************************************/
594 int
595 CheckFamilyList (char    *name )
596 {
597     char **listPtr = FamilyList;
598
599     while (listPtr != NULL && *listPtr != NULL)
600       {
601         if (strcmp (*listPtr, name) == 0)
602             return True;
603         listPtr++;
604       }
605
606     return False;
607 }
608
609 /*****************************************************************************
610  * Function:   AddFamilyToList (name)
611  *
612  *  add the name to the family list
613  *
614  *****************************************************************************/
615 char **
616 AddFamilyToList (char    *name )
617 {
618
619     FamilyList = (char **) _DtHelpCeAddPtrToArray ((void **) FamilyList,
620                                                             strdup(name));
621     return FamilyList;
622 }
623
624 /*****************************************************************************
625  * Function:   ScanDirectory
626  *
627  *  scan a directory looking for family files.
628  *
629  *****************************************************************************/
630 void
631 ScanDirectory (
632     char    *directory,
633     long    *ret_time)
634 {
635     DIR    *pDir;
636     struct stat buf;
637
638     char    fullName [MAXPATHLEN + 2];
639     char   *ptr;
640     char   *ext;
641
642     struct dirent *pDirent;
643
644     *ret_time = 0;
645     if (stat(directory, &buf) == -1)
646         return;
647
648     *ret_time = buf.st_mtime;
649
650     pDir = opendir (directory);
651     if (pDir == NULL)
652         return;
653
654     snprintf(fullName, sizeof(fullName), "%s%s", directory, SlashString);
655     ptr = fullName + strlen (fullName);
656
657     /*
658      * skip over the "." and ".." entries.
659      */
660     (void) readdir (pDir);
661     (void) readdir (pDir);
662     pDirent = readdir (pDir);
663     while (pDirent)
664       {
665         ext = GetExtension (pDirent->d_name);
666         if (strcmp (ext, Family_ext) == 0)
667           {
668             if (CheckFamilyList (pDirent->d_name) == False)
669               {
670                 AddFamilyToList (pDirent->d_name);
671
672                 strcpy (ptr, pDirent->d_name);
673                 FullFamilyName = (char **) _DtHelpCeAddPtrToArray(
674                                         (void **)FullFamilyName,
675                                         strdup(fullName));
676               }
677           }
678         else if (strcmp(ext, Ext_Hv) == 0 || strcmp(ext, Ext_Sdl) == 0)
679           {
680             strcpy (ptr, pDirent->d_name);
681             VolumeList  = (char **) _DtHelpCeAddPtrToArray((void **)VolumeList,
682                                                 strdup(pDirent->d_name));
683             FullVolName = (char **) _DtHelpCeAddPtrToArray((void **)FullVolName,
684                                                 strdup(fullName));
685           }
686
687         pDirent = readdir (pDir);
688       }
689
690     closedir(pDir);
691     return;
692 }
693
694 /*****************************************************************************
695  * Function:   FindFile
696  *
697  *  Resolves the environment variable for all possible paths.
698  *
699  *****************************************************************************/
700 char *
701 FindFile (
702     char *filename)
703 {
704     int     i;
705     int     trimExt = 0;
706     int     different;
707     char   *fileExt;
708     char   *ext;
709     struct stat status;
710
711     fileExt = GetExtension(filename);
712     if (*fileExt == '\0')
713         trimExt = 1;
714
715     i = 0;
716     while (VolumeList != NULL && VolumeList[i] != NULL)
717       {
718         if (trimExt)
719           {
720             ext = GetExtension(VolumeList[i]);
721             *ext = '\0';
722           }
723
724         different = strcmp(filename, VolumeList[i]);
725         if (trimExt)
726            *ext = '.';
727
728         if (!different && access(FullVolName[i], R_OK) == 0
729                         && stat(FullVolName[i], &status) == 0
730                                 && S_ISDIR(status.st_mode) == 0)
731             return (FullVolName[i]);
732
733         i++;
734       }
735
736     return NULL;
737 }
738
739 /*****************************************************************************
740  * Function:   ExpandPaths
741  *
742  *  Resolves the environment variable for all possible paths.
743  *
744  *****************************************************************************/
745 void
746 ExpandPaths (
747     char   *lang,
748     char   *type,
749     char   *env_var,
750     char   *default_str,
751     char ***list)
752 {
753     short strip;
754     char *ptr;
755     char *hPtr;
756     char *src;
757     char *pathName;
758     char *searchPath;
759
760     searchPath = getenv (env_var);
761     if (searchPath == NULL || *searchPath == '\0')
762       {
763         if (default_str == NULL)
764             return;
765
766         searchPath = default_str;
767       }
768
769     searchPath = strdup (searchPath);
770
771     *list = NULL;
772     src   = searchPath;
773     do 
774       {
775         ptr = strchr (src, ':'); 
776         if (ptr)
777             *ptr = '\0';
778
779         /*
780          * check to see if %H is declared. If so, we're going
781          * to have to trim it before saving as the directory path.
782          */
783         strip = False;
784         hPtr  = strrchr (src, '%');
785         if (hPtr != NULL)
786           {
787             hPtr++;
788             if (*hPtr == 'H')
789                 strip = True;
790           }
791
792         /*
793          * check to see if the path needs expanding
794          */
795         if (NULL != strchr (src, '%'))
796             pathName = _DtHelpCeExpandPathname (src, NULL, type, NULL, lang,
797                                         (_DtSubstitutionRec *) NULL, 0);
798         else
799             pathName = strdup(src);
800
801         if (pathName)
802           {
803             GetPath (pathName, strip, list);
804             free (pathName);
805           }
806
807         if (ptr)
808           {
809             *ptr = ':';
810             ptr++;
811           }
812         src = ptr;
813       } while (src && *src);
814
815     free(searchPath);
816 }
817
818 /*****************************************************************************
819  * Function:   CheckTimeStamps
820  *
821  * Check the time stamps on the volume dir to determine if
822  * it needs regenerating.
823  *
824  *****************************************************************************/
825 int
826 CheckTimeStamps (
827     XrmDatabase   db,
828     char        **dir_list)
829 {
830     long         timeVal;
831     char        *value;
832     struct stat  buf;
833
834     while (*dir_list != NULL)
835       {
836         if (stat(*dir_list, &buf) == -1)
837             buf.st_mtime = 0;
838
839         value = _DtHelpCeGetResourceString(db, *dir_list,
840                                                 "TimeStamp", "timeStamp");
841         timeVal = atol(value);
842         if (timeVal != buf.st_mtime)
843             return 1;
844
845         dir_list++;
846       }
847
848     return 0;
849 }
850
851 /*****************************************************************************
852  * Function:   CheckInfo
853  *
854  *  Check the information in the volume to determine if it needs regenerating.
855  *
856  *****************************************************************************/
857 int
858 CheckInfo (
859     char *file)
860 {
861     int         result = 1;
862     char        **list1, **list2;
863     char        **volDirList;
864     XrmDatabase db;
865
866     db = XrmGetFileDatabase (file);
867     if (db != NULL)
868       {
869         volDirList = _DtHelpCeGetResourceStringArray(db, NULL,
870                                                         "DirList", "dirList");
871         if (volDirList != NULL)
872           {
873             result = 0;
874             list1  = volDirList;
875             list2  = FUserList;
876             while (result == 0 && *list1 != NULL
877                                         && list2 != NULL && *list2 != NULL)
878               {
879                 result = strcmp(*list1, *list2);
880                 list1++;
881                 list2++;
882               }
883
884             if (list2 != NULL && *list2 != NULL)
885                 result = 1;
886
887             list2  = FSysList;
888             while (result == 0 && *list1 != NULL
889                                         && list2 != NULL && *list2 != NULL)
890               {
891                 result = strcmp(*list1, *list2);
892                 list1++;
893                 list2++;
894               }
895
896             if (list2 != NULL && *list2 != NULL)
897                 result = 1;
898
899             list2  = VUserList;
900             while (result == 0 && *list1 != NULL
901                                         && list2 != NULL && *list2 != NULL)
902               {
903                 result = strcmp(*list1, *list2);
904                 list1++;
905                 list2++;
906               }
907
908             if (list2 != NULL && *list2 != NULL)
909                 result = 1;
910
911             list2  = VSysList;
912             while (result == 0 && *list1 != NULL
913                                         && list2 != NULL && *list2 != NULL)
914               {
915                 result = strcmp(*list1, *list2);
916                 list1++;
917                 list2++;
918               }
919
920             if (*list1 != NULL || (list2 != NULL && *list2 != NULL))
921                 result = 1;
922
923             if (result == 0)
924                 result = CheckTimeStamps(db, volDirList);
925
926             _DtHelpCeFreeStringArray(volDirList);
927           }
928         XrmDestroyDatabase(db);
929       }
930
931     return result;
932 }
933
934 /***************************************************************************** 
935  *                      Main routine
936  *****************************************************************************/
937 int
938 main(
939     int    argc,
940     char  *argv[] )
941 {
942     int      i;
943     int      result;
944     int      foundFamily;
945     int      foundVolumes;
946     int      usedUser = 0;
947     int      doGen    = 0;
948
949     char     tmpVolume  [MAXPATHLEN + 2];
950     char     tmpVolumeTemp[sizeof(tmpVolume)];
951     char     tmpVolume2 [MAXPATHLEN + 2];
952     char     tmpTopic   [MAXPATHLEN + 2];
953     char     tmpHeader  [MAXPATHLEN + 2];
954     char     headerName [MAXPATHLEN + 2];
955     char     baseName   [MAXPATHLEN + 2];
956     char     baseNameTemp[sizeof(baseName)];
957     char     tempName   [MAXPATHLEN + 2];
958     char   **next;
959     char    *charSet;
960     char    *topicTitle;
961     char    *ptr;
962     char    *endDir;
963
964     long    preamble;
965     long    modTime;
966     FILE   *outVolume;
967     FILE   *outTopic;
968     FILE   *outHeader;
969     pid_t   childPid = (pid_t) -1;
970     CanvasHandle canvasHandle;
971
972     myName = strrchr (argv[0], '/');
973     if (myName)
974         myName++;
975     else
976         myName = argv[0];
977
978    /*
979     * have to do a setlocale here, so that the usage message is in the
980     * correct language.
981     */
982    Lang = getenv ("LANG");
983
984    /*
985     * use the default if no lang is specified.
986     */
987    if (Lang == NULL || *Lang == '\0')
988         Lang = (char*)C_String;
989
990     setlocale(LC_ALL, "");
991     _DtEnvControl(DT_ENV_SET);
992
993     /*
994      * now process the arguments
995      */
996     for (i = 1; i < argc; i++)
997       {
998         if (argv[i][0] == '-')
999           {
1000             if (argv[i][1] == 'd' && i + 1 < argc)
1001                 App_args.dir = argv[++i];
1002             else if (argv[i][1] == 'f' && i + 1 < argc)
1003                 App_args.file = argv[++i];
1004             else if (argv[i][1] == 'g')
1005                 doGen = 1;
1006             else if (argv[i][1] == 'l' && i + 1 < argc)
1007                 App_args.lang = argv[++i];
1008             else
1009               {
1010                 fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1011                 exit (1);
1012               }
1013           }
1014         else
1015           {
1016             fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1017             exit (1);
1018           }
1019       }
1020
1021     /*
1022      * get the language we are working with.
1023      */
1024     if (App_args.lang != NULL)
1025       {
1026         /*
1027          * Set the locale! Since the user has specified a (likely)
1028          * different language to do the processing in, we need to
1029          * do a setlocale to work with the new language.
1030          */
1031         Lang = App_args.lang;
1032         if (setlocale(LC_ALL, Lang) == NULL)
1033           {
1034             fprintf (stderr, (GetMessage(1, 20,
1035                         "%s: Invalid system language specified - %s\n")),
1036                                 myName, Lang);
1037             exit (1);
1038           }
1039         _DtEnvControl(DT_ENV_SET);
1040       }
1041     Lang = strdup(Lang);
1042
1043     /*
1044      * get the directory to work in
1045      */
1046     if (NULL == App_args.dir)
1047       {
1048         fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1049         exit (1);
1050       }
1051
1052     if (App_args.dir[0] != '/')
1053       {
1054         if (getcwd (baseName, MAXPATHLEN) == NULL)
1055           {
1056             fprintf (stderr, (GetMessage (1, 18,
1057  "%s: Unable to access current working directory - error status number %d\n")),
1058                                 myName, errno);
1059             exit (1);
1060           }
1061         snprintf(baseNameTemp, sizeof(baseNameTemp), "%s/%s", baseName, App_args.dir);
1062         strcpy(baseName, baseNameTemp);
1063       }
1064     else
1065         snprintf(baseName, sizeof(baseName), "%s", App_args.dir);
1066
1067     /*
1068      * make sure the directory exists
1069      */
1070     ptr = _DtHelpCeExpandPathname (baseName, NULL, "help", NULL, Lang,
1071                                         (_DtSubstitutionRec *) NULL, 0);
1072     if (ptr == NULL || *ptr == '\0')
1073       {
1074         fprintf (stderr,
1075                 (GetMessage (1, 15, "%s: Destination directory missing\n")),
1076                         myName);
1077         exit (1);
1078       }
1079
1080     snprintf(tmpVolume, sizeof(tmpVolume), "%s", ptr);
1081     if (tmpVolume[strlen (tmpVolume) - 1] != '/') {
1082         snprintf(tmpVolumeTemp, sizeof(tmpVolumeTemp), "%s%s", tmpVolume, SlashString);
1083         strcpy(tmpVolume, tmpVolumeTemp);
1084     }
1085
1086     free (ptr);
1087
1088     /*
1089      * march down the path, checking that
1090      *          1) it exists
1091      *          2) the caller has access permission.
1092      *          3) resolve all symbolic links
1093      */
1094     endDir = strchr (tmpVolume, '/');
1095     if (endDir != NULL)
1096       {
1097         endDir++;
1098         endDir = strchr (endDir, '/');
1099       }
1100     while (endDir && *endDir != '\0')
1101       {
1102         /*
1103          * remember the rest of the string (including the slash)
1104          * and strip the trailing slash from the directory path.
1105          */
1106         snprintf(tmpVolume2, sizeof(tmpVolume2), "%s", endDir);
1107         *endDir = '\0';
1108
1109         /*
1110          * trace the path and copy the new string into the old buffer.
1111          */
1112         ptr = _DtHelpCeTracePathName(tmpVolume);
1113         if (ptr != NULL)
1114           {
1115             snprintf(tmpVolume, sizeof(tmpVolume), "%s", ptr);
1116             free (ptr);
1117           }
1118
1119         if (access (tmpVolume, F_OK) == -1)
1120           {
1121             switch (errno)
1122               {
1123                 case ENOTDIR:
1124                         ptr = GetMessage (1, 2, (char*)NotDirectory);
1125                         fprintf (stderr, ptr, myName, tmpVolume);
1126                         exit (1);
1127
1128                 case EACCES:
1129                         ptr = GetMessage (1, 3, (char*)SuperMsg);
1130                         fprintf (stderr, ptr, myName, tmpVolume);
1131                         exit (1);
1132
1133                 case ENOENT:
1134                         if (mkdir(tmpVolume,
1135                             (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) == -1
1136                                 && errno != EEXIST && errno != EROFS)
1137                           {
1138                             switch (errno)
1139                               {
1140                                 case ENOTDIR:
1141                                         ptr = GetMessage(1,2,
1142                                                         (char*)NotDirectory);
1143                                         break;
1144
1145                                 case EACCES:
1146                                         ptr = GetMessage(1, 3, 
1147                                                         (char*)SuperMsg);
1148                                         break;
1149
1150                                 case ENOENT:
1151                                         ptr = GetMessage(1, 4,
1152                                     "%s: Element of %s does not exist\n");
1153                                         break;
1154
1155                                 case ENOSPC:
1156                                         ptr = GetMessage (1, 5,
1157                                 "%s: File system containing %s is full\n");
1158                                         break;
1159
1160                                 default:
1161                                         ptr = GetMessage(1,6,
1162                                                         (char*)GeneralAccess);
1163                                         break;
1164                               }
1165                             fprintf (stderr, ptr, myName, tmpVolume, errno);
1166                             exit (1);
1167                           }
1168                         break;
1169                 default:
1170                         ptr = GetMessage (1, 6, (char*)GeneralAccess);
1171                         fprintf (stderr, ptr, myName, tmpVolume, errno);
1172                         exit (1);
1173               }
1174           }
1175
1176         /*
1177          * point to the end of the string (past where the slash will go)
1178          */
1179         endDir = tmpVolume + strlen(tmpVolume) + 2;
1180
1181         /*
1182          * append the rest of the directory spec that hasn't been checked.
1183          */
1184         strcat (tmpVolume, tmpVolume2);
1185         endDir = strchr (endDir, '/');
1186       }
1187
1188
1189     /*
1190      * get temporary files for the volume and topic file.
1191      */
1192     snprintf(tmpVolumeTemp, sizeof(tmpVolumeTemp), "%s%s", tmpVolume, App_args.file);
1193     strcpy(tmpVolume, tmpVolumeTemp);
1194
1195     (void) strcpy (tmpHeader, tmpVolume);
1196     (void) strcpy (tmpTopic, tmpVolume);
1197
1198     snprintf(tmpVolumeTemp, sizeof(tmpVolumeTemp), "%s%s", tmpVolume, Ext_Hv);
1199     strcpy(tmpVolume, tmpVolumeTemp);
1200     (void) strcat (tmpHeader, "00.ht");
1201     (void) strcat (tmpTopic , "01.ht");
1202
1203     result = access (tmpVolume, F_OK);
1204
1205     /*
1206      * If it exists, make sure the invoker can write to it.
1207      */
1208     if (result == 0)
1209       {
1210         if (access (tmpVolume, W_OK) == -1)
1211           {
1212             if (errno == EROFS)
1213                 ptr = GetMessage (1, 7,
1214                         "%s: File system containing %s is read only\n");
1215             else if (errno == EACCES)
1216                 ptr = GetMessage (1, 8,
1217                         "%s: Requires root permission to write to %s\n");
1218             else
1219                 ptr = GetMessage (1, 9, (char*)WriteInvalid);
1220
1221             fprintf (stderr, ptr, myName, tmpVolume, errno);
1222             exit (1);
1223           }
1224       }
1225     else if (result == -1 && errno != ENOENT)
1226       {
1227         ptr = GetMessage (1, 6, (char*)GeneralAccess);
1228         fprintf (stderr, ptr, myName, tmpVolume, errno);
1229         exit (1);
1230       }
1231     else /* file does not exist */
1232         doGen = 1;
1233
1234
1235     /*
1236      * Find out if we have any paths to check.
1237      */
1238     ExpandPaths(Lang, "families", DtUSER_FILE_SEARCH_ENV, NULL, &FUserList);
1239     ExpandPaths(Lang, "volumes", DtUSER_FILE_SEARCH_ENV, NULL, &VUserList);
1240     ExpandPaths(Lang, "families", DtSYS_FILE_SEARCH_ENV ,
1241                                         DtDEFAULT_SYSTEM_PATH, &FSysList);
1242     ExpandPaths(Lang, "volumes", DtSYS_FILE_SEARCH_ENV ,
1243                                         DtDEFAULT_SYSTEM_PATH, &VSysList);
1244     if (((FUserList == NULL || *FUserList == NULL) &&
1245         (FSysList  == NULL || *FSysList  == NULL)) ||
1246         ((VUserList == NULL || *VUserList == NULL) &&
1247         (VSysList  == NULL || *VSysList  == NULL)))
1248       {
1249         ptr = GetMessage (1, 10, "%s: Search Path empty\n");
1250         fprintf (stderr, ptr, myName);
1251         exit (1);
1252       }
1253
1254     /*
1255      * If we already haven't determined that the volume needs (re)generating
1256      * check the info squirreled away in the old volume.
1257      */
1258     if (doGen == 0)
1259         doGen = CheckInfo(tmpVolume);
1260
1261     /*
1262      * the volume doesn't need (re)generating.
1263      * exit now.
1264      */
1265     if (doGen == 0)
1266         exit(0);
1267
1268     /*
1269      * create a canvas for the functions.
1270      */
1271     canvasHandle = _DtHelpCeCreateDefCanvas();
1272     if (canvasHandle == NULL)
1273       {
1274         fprintf (stderr, GetMessage(1, 19,"%s: Unable to allocate memory\n"),
1275                                                                 myName);
1276         MyExit (1, -1);
1277       }
1278
1279     /*
1280      * open the individual files that will hold the browser information.
1281      * <file>.hv
1282      */
1283     outVolume = fopen (tmpVolume, "w");
1284     if (outVolume == NULL)
1285       {
1286         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1287         fprintf (stderr, ptr, myName, tmpVolume, errno);
1288         _DtHelpCeDestroyCanvas(canvasHandle);
1289         MyExit (1, -1);
1290       }
1291
1292     /*
1293      * <file>00.ht
1294      */
1295     outTopic = fopen (tmpTopic, "w");
1296     if (outTopic == NULL)
1297       {
1298         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1299         fprintf (stderr, ptr, myName, tmpTopic, errno);
1300         (void) unlink (tmpVolume);
1301         _DtHelpCeDestroyCanvas(canvasHandle);
1302         MyExit (1, -1);
1303       }
1304
1305     /*
1306      * <file>01.ht
1307      */
1308     outHeader = fopen (tmpHeader, "w");
1309     if (outHeader == NULL)
1310       {
1311         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1312         fprintf (stderr, ptr, myName, tmpHeader, errno);
1313         (void) unlink (tmpVolume);
1314         (void) unlink (tmpTopic);
1315         _DtHelpCeDestroyCanvas(canvasHandle);
1316         MyExit (1, -1);
1317       }
1318
1319     /*
1320      * fork off the dtksh script that will put up a dialog
1321      * telling the user that we're building help browser
1322      * information.
1323      */
1324 #ifdef  __hpux
1325     childPid = vfork();
1326 #else
1327     childPid = fork();
1328 #endif
1329     /*
1330      * if this is the child, exec the dthelpgen.ds script.
1331      */
1332     if (childPid == 0)
1333       {
1334         execlp("dthelpgen.ds", "dthelpgen.ds",
1335                                 ((char *) 0), ((char *) 0), ((char *) 0));
1336         _exit(1);
1337       }
1338
1339     /*
1340      * initialize the main topic
1341      */
1342     strcpy (TopicName, App_args.file);
1343     strcat (TopicName, "01.ht");
1344
1345     strcpy (headerName, App_args.file);
1346     strcat (headerName, "00.ht");
1347
1348     /*
1349      * The original dthelpgen extracts the CDE Standard name from
1350      * its message catalogue( set 2/msg 1 ).
1351      * But on IBM ODE, this is a problem. For example,
1352      * fr_FR's dthelpgen.cat has 
1353      *    fr_FR.ISO-8859-1 in set 2/msg 1.
1354      * Correct Fr_FR's message catalogue must have,
1355      *    fr_FR.IBM-850
1356      * there. But current IBM ode's Makefile cannot do this. Instead put
1357      *    fr_FR.ISO-8859-1. ( That is "do nothing" ).
1358      * To fix this, dthelpgen converts the current IBM LANG to CDE
1359      * standard name with _DtLcx*() function provided by libDtHelp.a as
1360      * internal API.
1361      */
1362 #ifdef _AIX
1363     {
1364         _DtXlateDb db = NULL;
1365         int  ret;
1366         char plat[_DtPLATFORM_MAX_LEN];
1367         int  execver;
1368         int  compver;
1369         char *ret_stdLocale;
1370         char *ret_stdLangTerr;
1371         char *ret_stdCodeset;
1372         char *ret_stdModifier;
1373
1374         ret = _DtLcxOpenAllDbs( &db );
1375         if ( !ret ) {
1376             ret = _DtXlateGetXlateEnv( db, plat, &execver, &compver );
1377             if ( !ret ) {
1378                 ret =  _DtLcxXlateOpToStd( db, plat, execver,
1379                                         DtLCX_OPER_SETLOCALE,
1380                                         setlocale( LC_MESSAGES, NULL ),
1381                                         &ret_stdLocale, &ret_stdLangTerr,
1382                                         &ret_stdCodeset, &ret_stdModifier );
1383                 if ( !ret ) {
1384                     charSet = strdup( ret_stdLocale );
1385                 } else {
1386                     charSet = "C.ISO-8859-1";
1387                 }
1388             } else {
1389                 charSet = "C.ISO-8859-1";
1390             }
1391             ret = _DtLcxCloseDb( &db );
1392         } else {
1393             charSet = "C.ISO-8859-1";
1394         }
1395
1396     }
1397 #else /* _AIX */
1398     charSet    = strdup (GetMessage (2, 1, "C.ISO-8859-1"));
1399 #endif /* _AIX */
1400     topicTitle = strdup (GetMessage (2, 2, "Welcome to Help Manager"));
1401
1402     fprintf (outHeader, (GetMessage (3, 1, (char*)defaultTopic)),
1403                                                 charSet, topicTitle);
1404
1405     fprintf (outHeader, (GetMessage (3, 2, (char*)defaultTitle14)), topicTitle);
1406     free(topicTitle);
1407
1408     preamble = ftell (outHeader);
1409     fprintf (outHeader, "%s\n", GetMessage (2, 3, (char*)defaultTextBody));
1410
1411     /*
1412      * loop through the directories looking for all the unique families
1413      * and -all- the volumes.
1414      */
1415     fprintf (outVolume, "!# Last modification time stamp per directory\n");
1416     next = FUserList;
1417     while (next != NULL && *next != NULL)
1418       {
1419         ScanDirectory(*next, &modTime);
1420         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1421         next++;
1422       }
1423     next = FSysList;
1424     while (next != NULL && *next != NULL)
1425       {
1426         ScanDirectory(*next, &modTime);
1427         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1428         next++;
1429       }
1430
1431     next = VUserList;
1432     while (next != NULL && *next != NULL)
1433       {
1434         ScanDirectory(*next, &modTime);
1435         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1436         next++;
1437       }
1438     next = VSysList;
1439     while (next != NULL && *next != NULL)
1440       {
1441         ScanDirectory(*next, &modTime);
1442         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1443         next++;
1444       }
1445
1446     fprintf (outVolume, "*.charSet:     %s\n", charSet);
1447     free(charSet);
1448     fprintf (outVolume, "\n!# Topic filenames and offsets\n");
1449
1450     /*
1451      * Now create families.
1452      */
1453     foundVolumes = 0;
1454     foundFamily  = 0;
1455     for (next = FullFamilyName; next != NULL && *next != NULL; next++)
1456       {
1457         result = CreateFamily(canvasHandle,*next,outVolume,outHeader,outTopic);
1458         if (result == 0)
1459           {
1460             FamilyNum++;
1461             foundVolumes = 1;
1462             foundFamily  = 1;
1463           }
1464         else if (result == -2)
1465             foundFamily  = 1;
1466       }
1467
1468     if (foundFamily == 0)
1469         fprintf(stderr,
1470                 GetMessage(1, 16, "%s: Zero Family files found\n"), myName);
1471     else if (foundVolumes == 0)
1472         fprintf (stderr,
1473                 GetMessage (1, 17, "%s: Zero Volume files found\n"), myName);
1474
1475     /*
1476      * Clean up
1477      */
1478     if (FamilyList != NULL)
1479         _DtHelpCeFreeStringArray(FamilyList);
1480     if (FullFamilyName != NULL)
1481         _DtHelpCeFreeStringArray(FullFamilyName);
1482     if (VolumeList != NULL)
1483         _DtHelpCeFreeStringArray(VolumeList);
1484     if (FullVolName != NULL)
1485         _DtHelpCeFreeStringArray(FullVolName);
1486
1487     /*
1488      * If no family or volume files were found,
1489      * write out the alternative preamble
1490      */
1491     if (foundFamily == 0 || foundVolumes == 0)
1492       {
1493         fseek (outHeader, preamble, 0);
1494         fprintf (outHeader, "%s\n", GetMessage (2, 5, (char*)defaultAlternate));
1495       }
1496
1497     /*
1498      * write the ending message and finish the topic.
1499      */
1500     fprintf (outHeader, "</TOPIC>\n");
1501
1502     /*
1503      * write out the volume resouces
1504      */
1505     fprintf (outVolume, "\n\n!# Top (or home) topic filename and offset\n");
1506     fprintf (outVolume, "*.%s.filepos:   0\n" , ParentName);
1507     fprintf (outVolume, "*.%s.filename:  %s\n", ParentName, headerName);
1508
1509     fprintf (outVolume, "\n\n!# Volume Title\n");
1510     fprintf (outVolume, "*.title:     %s\n",
1511                       GetMessage (2, 4, "Help - Top Level"));
1512
1513     /*
1514      * top topic
1515      */
1516     fprintf (outVolume, "\n\n!# Topic Home Location\n");
1517     fprintf (outVolume, "*.topTopic:  %s\n", ParentName);
1518
1519     /*
1520      * topic hierarchy
1521      */
1522     fprintf (outVolume, "\n\n!# Topic Hierarchy\n");
1523     fprintf (outVolume, "*.%s.parent:  \n", ParentName);
1524     for (next = TopicList; next && *next; next++)
1525         fprintf (outVolume, "*.%s.parent: %s\n", *next, ParentName);
1526
1527     /*
1528      * topic list
1529      */
1530     fprintf (outVolume, "\n\n!# Topic List\n");
1531     fprintf (outVolume, "*.topicList: %s", ParentName);
1532     next = TopicList;
1533     while (next && *next)
1534       {
1535         fprintf (outVolume, " \\\n      %s", *next);
1536         next++;
1537       }
1538     fprintf (outVolume, "\n");
1539
1540     /*
1541      * The paths used to create this information.
1542      */
1543     fprintf (outVolume, "\n\n!# Paths Searched\n");
1544     fprintf (outVolume, "*.dirList: ");
1545
1546     next = FUserList;
1547     while (next != NULL && *next != NULL)
1548       {
1549         fprintf (outVolume, " \\\n      %s", *next);
1550         next++;
1551       }
1552     next = FSysList;
1553     while (next != NULL && *next != NULL)
1554       {
1555         fprintf (outVolume, " \\\n      %s", *next);
1556         next++;
1557       }
1558     next = VUserList;
1559     while (next != NULL && *next != NULL)
1560       {
1561         fprintf (outVolume, " \\\n      %s", *next);
1562         next++;
1563       }
1564     next = VSysList;
1565     while (next != NULL && *next != NULL)
1566       {
1567         fprintf (outVolume, " \\\n      %s", *next);
1568         next++;
1569       }
1570     fprintf (outVolume, "\n");
1571
1572     /*
1573      * close the volumes.
1574      */
1575     fclose (outVolume);
1576     fclose (outHeader);
1577     fclose (outTopic);
1578
1579     if (TopicList != NULL)
1580         _DtHelpCeFreeStringArray(TopicList);
1581
1582     if (FUserList != NULL)
1583         _DtHelpCeFreeStringArray(FUserList);
1584     if (FSysList != NULL)
1585         _DtHelpCeFreeStringArray(FSysList);
1586     if (VUserList != NULL)
1587         _DtHelpCeFreeStringArray(VUserList);
1588     if (VSysList != NULL)
1589         _DtHelpCeFreeStringArray(VSysList);
1590
1591     _DtHelpCeDestroyCanvas(canvasHandle);
1592
1593     MyExit (0, childPid);
1594 }