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