dthelp: compiler warning and coverity warning 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 librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $TOG: helpgen.c /main/8 1998/04/20 12:52:36 mgreess $ */
24 #include <dirent.h>
25 #include <errno.h>
26 #include <locale.h>
27 #include <nl_types.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/param.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <X11/Xlib.h>
38 #include <X11/Xresource.h>
39 #include <X11/Intrinsic.h>
40
41 #include <Dt/Help.h>
42 #include <Dt/EnvControlP.h>
43
44 #include "HelpP.h"        /* in DtHelp library */
45 #include "GenUtilsP.h"    /* in DtHelp library */
46 #include "ObsoleteP.h"    /* in DtHelp library */
47 #include "bufioI.h"       /* for AccessI.h     */
48 #include "Access.h"       /* in DtHelp library */
49 #include "AccessP.h"      /* in DtHelp library */
50 #include "AccessI.h"      /* in DtHelp library */
51 #include "AccessCCDFI.h"  /* in DtHelp library */
52 #include "StringFuncsI.h" /* in DtHelp library */
53
54 #ifdef _AIX
55 #include <LocaleXlate.h>
56 #endif
57
58 #ifndef NL_CAT_LOCALE
59 static const int NL_CAT_LOCALE = 0;
60 #endif
61
62 #ifndef CDE_INSTALLATION_TOP
63 #define CDE_INSTALLATION_TOP "/usr/dt"
64 #endif
65
66 #ifndef CDE_CONFIGURATION_TOP
67 #define CDE_CONFIGURATION_TOP "/etc/dt"
68 #endif
69
70 #ifndef DtSYS_FILE_SEARCH_ENV
71 #define DtSYS_FILE_SEARCH_ENV          "DTHELPSEARCHPATH"
72 #endif
73
74 #ifndef DtUSER_FILE_SEARCH_ENV
75 #define DtUSER_FILE_SEARCH_ENV         "DTHELPUSERSEARCHPATH"
76 #endif
77
78 /*****************************************************************************
79  *      defines
80  *****************************************************************************/
81
82 #define VOLUME_EXT      ".hv"
83 #define FAMILY_EXT      ".hf"
84
85 /*****************************************************************************
86  *      static strings.
87  *****************************************************************************/
88 static  const char *ShellCmd    = "sh";
89 static  const char *UsageStr    =
90         "%s -dir <directory> [-generate] [-file <name>] [-lang <language>]\n";
91 static  const char *TopLocId    = "_hometopic";
92 static  const char *SlashString = "/";
93 static  const char *C_String    = "C";
94 static  const char *DefCharSet  = "C.ISO-8859-1";
95 static  const char *Family_ext  = FAMILY_EXT;
96 static  const char *Ext_Hv      = ".hv";
97 static  const char *Ext_Sdl     = ".sdl";
98
99 static  const char *SuperMsg =
100              "%s: Access denied for directory %s\nTry running as super user?\n";
101 static  const char *GeneralAccess =
102                         "%s: Unable to access %s - error status number  %d\n";
103 static  const char *NotDirectory = "%s: Element of %s is not a directory\n";
104 static  const char *WriteInvalid = "%s: Write to %s invalid\n";
105 static  const char *defaultTopic = "<TOPIC charset %s>\n";
106 static  const char *defaultTitle12 =
107         "<TYPE serif><WEIGHT bold><SIZE 12><ANGLE italic>\n%s\n</ANGLE></SIZE></WEIGHT></TYPE>\n";
108 static  const char *defaultTitle14 =
109         "<TITLE><TYPE serif><WEIGHT bold><SIZE 14>\n%s\n</SIZE></WEIGHT></TYPE></TITLE>\n";
110
111 static  const char *defaultTextBody =
112 "<ABBREV>Welcome to the Help Manager</ABBREV> \n\
113 <PARAGRAPH>Each of the titles listed below represents a <ANGLE italic> \n\
114 product family</> that has installed and registered its online help.  Each  \n\
115 title (and icon) is a hyperlink that lists the help within the family.</> \n\
116 <PARAGRAPH after 0 first 1 left 3 label \"<CHAR C.DT-SYMBOL-1><0xB7></>\">To \n\
117 display a list of the help available for a product family, choose its \n\
118 title (underlined text) or icon.</PARAGRAPH>  \n\
119 <PARAGRAPH after 0 first 1 left 3 label \"<CHAR C.DT-SYMBOL-1><0xB7></>\">\n\
120 Within a product  \n\
121 family, find the help you want to view, then choose its title.</PARAGRAPH> \n\
122 <PARAGRAPH first 1 left 3 label \"<CHAR C.DT-SYMBOL-1><0xB7></>\"> \n\
123 If you need help while using help windows, press F1.</PARAGRAPH>";
124
125 static  const char *defaultAlternate =
126 "<ABBREV>Welcome to the Help Manager</ABBREV> \n\
127 <LINK 0 \"Help4Help How-To-Register-Help\"> \n\
128 <TYPE serif><WEIGHT bold><SIZE 12><ANGLE italic> \n\
129 Note:\\ \\ \\ No Help Registered</SIZE></WEIGHT></TYPE></></LINK> \n\
130 <PARAGRAPH leftindent 3 firstindent 3> \n\
131 <WEIGHT bold>No product families have registered their online help \n\
132 files for browsing.</>  Help may be available for some applications by \n\
133 choosing Help commands directly within the applications.</>";
134
135 /*****************************************************************************
136  *      global variables.
137  *****************************************************************************/
138 char     *myName;
139 char     *Lang        = NULL;
140 char     *ParentName  = "_HOMETOPIC";
141
142 char    **TopicList   = NULL;
143
144 /* The family search list */
145 char    **FUserList    = NULL;
146 char    **FSysList     = NULL;
147
148 /* The volume search list */
149 char    **VUserList    = NULL;
150 char    **VSysList     = NULL;
151
152 char    **FamilyList     = NULL;        /* the names of the unique families */
153 char    **FullFamilyName = NULL;        /* the fully qualified family names */
154 char    **VolumeList     = NULL;        /* the names (only) of volume       */
155 char    **FullVolName    = NULL;        /* the fully qualified volume names */
156
157 char      TopicName [MAXPATHLEN + 2];
158
159 int       FamilyNum  = 0;
160
161 /* Global Message Catalog file names */
162 /*****************************************************************************
163  *      Private Function Declarations
164  *****************************************************************************/
165 extern  char    *FindFile (char *filename);
166
167 /*****************************************************************************
168  *      options and resources
169  *****************************************************************************/
170 typedef struct
171 {
172     char        *dir;
173     char        *file;
174     char        *lang;
175 } ApplicationArgs, *ApplicationArgsPtr;
176
177 static  ApplicationArgs App_args =
178   {
179         NULL,
180         "browser",
181         NULL,
182   };
183
184 /*****************************************************************************
185  * void MyExit(exit_val, pid)
186  *****************************************************************************/
187 void
188 MyExit (
189     int    exit_val,
190     pid_t  pid)
191 {
192   if (pid != ((pid_t) -1))
193       (void) kill(pid, SIGKILL);
194
195   exit (exit_val);
196 }
197
198 /*****************************************************************************
199  * char *GetMessage(set, n, s)
200  *****************************************************************************/
201 char *
202 GetMessage (
203     int    set,
204     int    n,
205     char  *s)
206 {
207    char *msg;
208    char *lang;
209    char  *catFileName=NULL;
210    static nl_catd nlmsg_fd;
211    static int first = 1;
212
213    if ( first ) 
214      {
215
216        /* Setup our default message catalog names if none have been set! */
217        /* Setup the short and long versions */
218 #ifdef __ultrix
219         catFileName = "dthelpgen.cat"; 
220 #else 
221         catFileName = "dthelpgen";
222 #endif
223         first = 0;
224
225         if (strcmp (Lang, "C") == 0) 
226           /*
227            * If LANG is not set or if LANG=C, then there
228            * is no need to open the message catalog - just
229            * return the built-in string "s".
230            */
231           nlmsg_fd = (nl_catd) -1;
232         else
233           nlmsg_fd = catopen(catFileName, NL_CAT_LOCALE);
234       }
235     msg=catgets(nlmsg_fd,set,n,s);
236     return (msg);
237
238 }
239
240 /*****************************************************************************
241  * Boolean *GetPath(filename)
242  *****************************************************************************/
243 Boolean
244 GetPath (char *filename, short strip, char ***list )
245 {
246     char  *ptr;
247     char **next = *list;
248
249     if (strip)
250       {
251         ptr = strrchr (filename, '/');
252         if (ptr)
253             *ptr = '\0';
254         else
255             filename = "./";
256       }
257
258     while (next != NULL && *next != NULL && strcmp (*next, filename))
259         next++;
260
261     if (next == NULL || *next == NULL)
262         *list = (char **) _DtHelpCeAddPtrToArray ((void **) (*list),
263                                                         strdup(filename));
264
265     return False;
266 }
267
268 /*****************************************************************************
269  * char *GetExtension(filename)
270  *     char *filename  - name of file to get the extension from.
271  * return  a pointer to the extension of the file name
272  *****************************************************************************/
273 char *
274 GetExtension(char *filename )
275 {
276     char *ext;
277
278 /*
279  * WARNING...
280  * need multi-byte functionality here
281  */
282     ext  = strrchr(filename, '.');
283     if (ext)
284         return(ext); /* safe because ext not in middle of character */
285
286   return(""); /* never returns NULL */
287 }
288
289 /*****************************************************************************
290  * Function:   CreateVolumeLink
291  *
292  *              outTopic        the output stream.
293  *              volume_name     Searches for a volume by this name.
294  *
295  * Reads a volume database and creates a label paragraph entry.
296  *
297  *****************************************************************************/
298 int
299 CreateVolumeLink (
300     CanvasHandle         canvas,
301     FILE                *outTopic,
302     char                *volume_name )
303 {
304     int          result = -1;
305     char        *title      = NULL;
306     char        *charSet    = (char *) DefCharSet;
307     char        *abstract   = NULL;
308     char        *filename   = NULL;
309     char        *pathName   = NULL;
310     VolumeHandle volume = NULL;
311
312     pathName = FindFile (volume_name);
313     if (pathName != NULL && _DtHelpCeOpenVolume(canvas,pathName,&volume) == 0)
314       {
315         if (_DtHelpCeGetVolumeTitle (canvas, volume, &title) == 0)
316             result = 0;
317         else if (_DtHelpCeGetTopicTitle(canvas,volume,(char*)TopLocId,&title)
318                                         == True)
319             result = 0;
320
321         if (result == 0)
322           {
323             if (_DtHelpCeGetAsciiVolumeAbstract(canvas,volume,&abstract) == -1)
324                 abstract = NULL;
325
326             charSet = _DtHelpCeGetVolumeLocale(volume);
327             if (charSet == NULL)
328                 charSet = (char *) DefCharSet;
329           }
330         _DtHelpCeCloseVolume (canvas, volume);
331       }
332
333     if (result == 0)
334       {
335         fprintf (outTopic, (GetMessage(3, 4, "<CHARACTERSET %s>\n")), charSet);
336         fprintf (outTopic,"<LINK 0 \"%s %s\">\n", volume_name, (char*)TopLocId);
337         fprintf (outTopic, (GetMessage(3, 5, (char*)defaultTitle12)), title);
338         fprintf (outTopic, "</LINK>\n");
339
340         /*
341          * put the abstract information about this
342          * family in the header file
343          */
344         fprintf (outTopic, "%s", GetMessage (3, 3, "<P before 1 first 1 left 1>\n"));
345
346         if (abstract != NULL)
347           {
348             fprintf (outTopic, (GetMessage (3, 4, "<CHARACTERSET %s>\n")),
349                                                                 charSet);
350             fprintf (outTopic, "%s\n", abstract);
351             fprintf (outTopic, "</CHARACTERSET>\n");
352             free (abstract);
353           }
354         fprintf (outTopic, "</P>\n</CHARACTERSET>\n");
355       }
356
357     if (charSet != DefCharSet)
358         free(charSet);
359
360     if (title)
361         free ((void *) title);
362     if (filename)
363         free ((void *) filename);
364
365     return result;
366 }
367
368 /*****************************************************************************
369  * Function:   CreateFamily
370  *
371  *****************************************************************************/
372 int
373 CreateFamily (
374     CanvasHandle canvas,
375     char    *family_name,
376     FILE    *out_volume,
377     FILE    *out_header,
378     FILE    *out_topic )
379 {
380     int          result = -1;
381     int          count = 0;
382     long         filepos;
383     char        *charSet = NULL;
384     char        *title = NULL;
385     char        *abstract = NULL;
386     char        *list = NULL;
387     char        *token;
388     char        *ptr;
389     char        *bitmap = NULL;
390     char         familyName [20];       /* FAMILY%d */
391     char         bitmapName [MAXPATHLEN + 2];
392
393     XrmDatabase db;
394     char        *resType;
395     XrmValue    resValue;
396
397     db = XrmGetFileDatabase (family_name);
398     if (db)
399       {
400         /*
401          * get the title
402          */
403         if (XrmGetResource (db, "Family.Title", "family.title",
404                                                 &resType, &resValue))
405           {
406             title = (char *) resValue.addr;
407
408             /*
409              * get the abstract
410              */
411             if (XrmGetResource (db, "Family.Abstract", "family.abstract",
412                                                     &resType, &resValue))
413               {
414                 abstract = (char *) resValue.addr;
415
416                 /*
417                  * get the volumes list
418                  */
419                 if (XrmGetResource (db, "Family.Volumes", "family.volumes",
420                                                         &resType, &resValue))
421                   {
422                     list = (char *) resValue.addr;
423
424                     /*
425                      * get the character set
426                      */
427                     if (XrmGetResource (db, "Family.CharSet", "family.charSet",
428                                                     &resType, &resValue))
429                       {
430                         charSet = (char *) resValue.addr;
431
432                         /*
433                          * get the bitmap (optional)
434                          */
435                         if (XrmGetResource (db,
436                                         "Family.Bitmap", "family.bitmap",
437                                          &resType, &resValue))
438                             bitmap = (char *) resValue.addr;
439                       }
440                     else
441                       {
442                         fprintf (stderr,
443                                 (GetMessage (1, 14,
444                                     "%s: character set resource missing\n")),
445                                 family_name);
446                         return -1;
447                       }
448                   }
449                 else
450                   {
451                     fprintf (stderr,
452                                 (GetMessage (1, 13,
453                                         "%s: volumes resource missing\n")),
454                                 family_name);
455                     return -1;
456                   }
457               }
458             else
459               {
460                 fprintf (stderr,
461                         (GetMessage (1, 12, "%s: abstract resource missing\n")),
462                         family_name);
463                 return -1;
464               }
465           }
466         else
467           {
468             fprintf (stderr,
469                         (GetMessage (1, 11, "%s: title resource missing\n")),
470                         family_name);
471             return -1;
472           }
473         
474         if (title && abstract && list && charSet)
475           {
476             /*
477              * find out the position of the file pointer
478              */
479             filepos = ftell (out_topic);
480
481             /*
482              * write out the <TOPIC>
483              */
484             fprintf (out_topic, (GetMessage (3, 1, (char*)defaultTopic)),
485                                                         charSet, title);
486
487             /*
488              * write out the <TITLE>
489              */
490             fprintf (out_topic, (GetMessage (3, 2, (char*)defaultTitle14)),
491                                                                 title);
492             fprintf (out_topic, "%s", (GetMessage (3, 3, "<P before 1 first 1 left 1>\n")));
493             fprintf (out_topic, "%s\n", abstract);
494             fprintf (out_topic, "</P>\n");
495
496             do 
497               {
498                 token = NULL;
499                 list = _DtHelpCeGetNxtToken(list, &token);
500                 if (token && *token != '\0' && *token != '\n' &&
501                                 CreateVolumeLink (canvas,out_topic, token) == 0)
502                     count++;
503
504                 if (token && *token != '\0' && *token != '\n')
505                     free ((void *) token);
506
507               } while (list && *list != '\0');
508
509             if (count)
510               {
511                 result = 0;
512                 sprintf (familyName, "FAMILY%d", FamilyNum);
513                 fprintf (out_topic, "</PARAGRAPH>\n</TOPIC>\n");
514
515                 /*
516                  * Put the link information in the header file
517                  */
518                 fprintf (out_header,
519                         (GetMessage (3, 4, "<CHARACTERSET %s>\n")), charSet);
520                 fprintf (out_header, "<LINK 0 %s>\n", familyName);
521                 fprintf (out_header, (GetMessage (3, 5, (char*)defaultTitle12)),
522                                                                 title);
523                 fprintf (out_header, "</LINK>\n");
524
525                 /*
526                  * put the abstract information about this
527                  * family in the header file
528                  */
529                 if (NULL != bitmap && *bitmap != '/')
530                   {
531                     strcpy (bitmapName, family_name);
532                     ptr = strrchr (bitmapName, '/');
533                     if (ptr)
534                       {
535                         ptr++;
536                         *ptr = '\0';
537                         strcat (bitmapName, bitmap);
538                         bitmap = bitmapName;
539                       }
540                     else
541                         bitmap = NULL;
542                   }
543
544                 if (NULL != bitmap)
545                   {
546                     fprintf (out_header,
547                         (GetMessage (3, 6,
548                                 "<P before 1 first 1 left 1 graphic %s glink %s gtypelink 0>\n")),
549                         bitmap, familyName);
550                   }
551                 else
552                     fprintf (out_header, "%s", GetMessage (3, 3, "<P before 1 first 1 left 1>\n"));
553                 fprintf (out_header, "%s\n", abstract);
554                 fprintf (out_header, "</P></CHARACTERSET>\n");
555
556                 /*
557                  * put the information in the volume file.
558                  */
559                 fprintf (out_volume, "*.%s.filepos: %ld\n",
560                                                 familyName, filepos);
561                 fprintf (out_volume, "*.%s.filename: %s\n",
562                                                 familyName, TopicName);
563                 TopicList = (char **) _DtHelpCeAddPtrToArray (
564                                                 (void **) TopicList,
565                                                 strdup (familyName));
566               }
567             else
568               {
569                 /*
570                  * rewind back to the original starting position
571                  */
572                 fseek (out_topic, filepos, 0);
573
574                 /*
575                  * didn't find any volumes for this family.
576                  */
577                 result = -2;
578               }
579           }
580         XrmDestroyDatabase (db);
581       }
582       
583     free (token);
584     return result;
585 }
586
587 /*****************************************************************************
588  * Function:   CheckFamilyList (name)
589  *
590  *  See if this family has been seen
591  *
592  *****************************************************************************/
593 int
594 CheckFamilyList (char    *name )
595 {
596     char **listPtr = FamilyList;
597
598     while (listPtr != NULL && *listPtr != NULL)
599       {
600         if (strcmp (*listPtr, name) == 0)
601             return True;
602         listPtr++;
603       }
604
605     return False;
606 }
607
608 /*****************************************************************************
609  * Function:   AddFamilyToList (name)
610  *
611  *  add the name to the family list
612  *
613  *****************************************************************************/
614 char **
615 AddFamilyToList (char    *name )
616 {
617
618     FamilyList = (char **) _DtHelpCeAddPtrToArray ((void **) FamilyList,
619                                                             strdup(name));
620     return FamilyList;
621 }
622
623 /*****************************************************************************
624  * Function:   ScanDirectory
625  *
626  *  scan a directory looking for family files.
627  *
628  *****************************************************************************/
629 void
630 ScanDirectory (
631     char    *directory,
632     long    *ret_time)
633 {
634     DIR    *pDir;
635     struct stat buf;
636
637     char    fullName [MAXPATHLEN + 2];
638     char   *ptr;
639     char   *ext;
640
641     struct dirent *pDirent;
642
643     *ret_time = 0;
644     if (stat(directory, &buf) == -1)
645         return;
646
647     *ret_time = buf.st_mtime;
648
649     pDir = opendir (directory);
650     if (pDir == NULL)
651         return;
652
653     strcpy (fullName, directory);
654     strcat (fullName, 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     tmpVolume2 [MAXPATHLEN + 2];
951     char     tmpTopic   [MAXPATHLEN + 2];
952     char     tmpHeader  [MAXPATHLEN + 2];
953     char     headerName [MAXPATHLEN + 2];
954     char     baseName   [MAXPATHLEN + 2];
955     char     tempName   [MAXPATHLEN + 2];
956     char   **next;
957     char    *charSet;
958     char    *topicTitle;
959     char    *ptr;
960     char    *endDir;
961
962     long    preamble;
963     long    modTime;
964     FILE   *outVolume;
965     FILE   *outTopic;
966     FILE   *outHeader;
967     pid_t   childPid = (pid_t) -1;
968     CanvasHandle canvasHandle;
969
970     myName = strrchr (argv[0], '/');
971     if (myName)
972         myName++;
973     else
974         myName = argv[0];
975
976    /*
977     * have to do a setlocale here, so that the usage message is in the
978     * correct language.
979     */
980    Lang = getenv ("LANG");
981
982    /*
983     * use the default if no lang is specified.
984     */
985    if (Lang == NULL || *Lang == '\0')
986         Lang = (char*)C_String;
987
988     setlocale(LC_ALL, "");
989     _DtEnvControl(DT_ENV_SET);
990
991     /*
992      * now process the arguments
993      */
994     for (i = 1; i < argc; i++)
995       {
996         if (argv[i][0] == '-')
997           {
998             if (argv[i][1] == 'd' && i + 1 < argc)
999                 App_args.dir = argv[++i];
1000             else if (argv[i][1] == 'f' && i + 1 < argc)
1001                 App_args.file = argv[++i];
1002             else if (argv[i][1] == 'g')
1003                 doGen = 1;
1004             else if (argv[i][1] == 'l' && i + 1 < argc)
1005                 App_args.lang = argv[++i];
1006             else
1007               {
1008                 fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1009                 exit (1);
1010               }
1011           }
1012         else
1013           {
1014             fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1015             exit (1);
1016           }
1017       }
1018
1019     /*
1020      * get the language we are working with.
1021      */
1022     if (App_args.lang != NULL)
1023       {
1024         /*
1025          * Set the locale! Since the user has specified a (likely)
1026          * different language to do the processing in, we need to
1027          * do a setlocale to work with the new language.
1028          */
1029         Lang = App_args.lang;
1030         if (setlocale(LC_ALL, Lang) == NULL)
1031           {
1032             fprintf (stderr, (GetMessage(1, 20,
1033                         "%s: Invalid system language specified - %s\n")),
1034                                 myName, Lang);
1035             exit (1);
1036           }
1037         _DtEnvControl(DT_ENV_SET);
1038       }
1039     Lang = strdup(Lang);
1040
1041     /*
1042      * get the directory to work in
1043      */
1044     if (NULL == App_args.dir)
1045       {
1046         fprintf (stderr, (GetMessage(1,1, ((char*)UsageStr))), myName);
1047         exit (1);
1048       }
1049
1050     if (App_args.dir[0] != '/')
1051       {
1052         if (getcwd (baseName, MAXPATHLEN) == NULL)
1053           {
1054             fprintf (stderr, (GetMessage (1, 18,
1055  "%s: Unable to access current working directory - error status number %d\n")),
1056                                 myName, errno);
1057             exit (1);
1058           }
1059         strcat (baseName, "/");
1060         strcat (baseName, App_args.dir);
1061       }
1062     else
1063         strcpy (baseName, App_args.dir);
1064
1065     /*
1066      * make sure the directory exists
1067      */
1068     ptr = _DtHelpCeExpandPathname (baseName, NULL, "help", NULL, Lang,
1069                                         (_DtSubstitutionRec *) NULL, 0);
1070     if (ptr == NULL || *ptr == '\0')
1071       {
1072         fprintf (stderr,
1073                 (GetMessage (1, 15, "%s: Destination directory missing\n")),
1074                         myName);
1075         exit (1);
1076       }
1077
1078     (void) strcpy (tmpVolume, ptr);
1079     if (tmpVolume[strlen (tmpVolume) - 1] != '/')
1080         strcat(tmpVolume, SlashString);
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         strcpy (tmpVolume2, 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             strcpy (tmpVolume, 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     (void) strcat (tmpVolume, App_args.file);
1189
1190     (void) strcpy (tmpHeader, tmpVolume);
1191     (void) strcpy (tmpTopic, tmpVolume);
1192
1193     (void) strcat (tmpVolume, Ext_Hv);
1194     (void) strcat (tmpHeader, "00.ht");
1195     (void) strcat (tmpTopic , "01.ht");
1196
1197     result = access (tmpVolume, F_OK);
1198
1199     /*
1200      * If it exists, make sure the invoker can write to it.
1201      */
1202     if (result == 0)
1203       {
1204         if (access (tmpVolume, W_OK) == -1)
1205           {
1206             if (errno == EROFS)
1207                 ptr = GetMessage (1, 7,
1208                         "%s: File system containing %s is read only\n");
1209             else if (errno == EACCES)
1210                 ptr = GetMessage (1, 8,
1211                         "%s: Requires root permission to write to %s\n");
1212             else
1213                 ptr = GetMessage (1, 9, (char*)WriteInvalid);
1214
1215             fprintf (stderr, ptr, myName, tmpVolume, errno);
1216             exit (1);
1217           }
1218       }
1219     else if (result == -1 && errno != ENOENT)
1220       {
1221         ptr = GetMessage (1, 6, (char*)GeneralAccess);
1222         fprintf (stderr, ptr, myName, tmpVolume, errno);
1223         exit (1);
1224       }
1225     else /* file does not exist */
1226         doGen = 1;
1227
1228
1229     /*
1230      * Find out if we have any paths to check.
1231      */
1232     ExpandPaths(Lang, "families", DtUSER_FILE_SEARCH_ENV, NULL, &FUserList);
1233     ExpandPaths(Lang, "volumes", DtUSER_FILE_SEARCH_ENV, NULL, &VUserList);
1234     ExpandPaths(Lang, "families", DtSYS_FILE_SEARCH_ENV ,
1235                                         DtDEFAULT_SYSTEM_PATH, &FSysList);
1236     ExpandPaths(Lang, "volumes", DtSYS_FILE_SEARCH_ENV ,
1237                                         DtDEFAULT_SYSTEM_PATH, &VSysList);
1238     if (((FUserList == NULL || *FUserList == NULL) &&
1239         (FSysList  == NULL || *FSysList  == NULL)) ||
1240         ((VUserList == NULL || *VUserList == NULL) &&
1241         (VSysList  == NULL || *VSysList  == NULL)))
1242       {
1243         ptr = GetMessage (1, 10, "%s: Search Path empty\n");
1244         fprintf (stderr, ptr, myName);
1245         exit (1);
1246       }
1247
1248     /*
1249      * If we already haven't determined that the volume needs (re)generating
1250      * check the info squirreled away in the old volume.
1251      */
1252     if (doGen == 0)
1253         doGen = CheckInfo(tmpVolume);
1254
1255     /*
1256      * the volume doesn't need (re)generating.
1257      * exit now.
1258      */
1259     if (doGen == 0)
1260         exit(0);
1261
1262     /*
1263      * create a canvas for the functions.
1264      */
1265     canvasHandle = _DtHelpCeCreateDefCanvas();
1266     if (canvasHandle == NULL)
1267       {
1268         fprintf (stderr, GetMessage(1, 19,"%s: Unable to allocate memory\n"),
1269                                                                 myName);
1270         MyExit (1, -1);
1271       }
1272
1273     /*
1274      * open the individual files that will hold the browser information.
1275      * <file>.hv
1276      */
1277     outVolume = fopen (tmpVolume, "w");
1278     if (outVolume == NULL)
1279       {
1280         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1281         fprintf (stderr, ptr, myName, tmpVolume, errno);
1282         _DtHelpCeDestroyCanvas(canvasHandle);
1283         MyExit (1, -1);
1284       }
1285
1286     /*
1287      * <file>00.ht
1288      */
1289     outTopic = fopen (tmpTopic, "w");
1290     if (outTopic == NULL)
1291       {
1292         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1293         fprintf (stderr, ptr, myName, tmpTopic, errno);
1294         (void) unlink (tmpVolume);
1295         _DtHelpCeDestroyCanvas(canvasHandle);
1296         MyExit (1, -1);
1297       }
1298
1299     /*
1300      * <file>01.ht
1301      */
1302     outHeader = fopen (tmpHeader, "w");
1303     if (outHeader == NULL)
1304       {
1305         ptr = GetMessage (1, 9, (char*)WriteInvalid);
1306         fprintf (stderr, ptr, myName, tmpHeader, errno);
1307         (void) unlink (tmpVolume);
1308         (void) unlink (tmpTopic);
1309         _DtHelpCeDestroyCanvas(canvasHandle);
1310         MyExit (1, -1);
1311       }
1312
1313     /*
1314      * fork off the dtksh script that will put up a dialog
1315      * telling the user that we're building help browser
1316      * information.
1317      */
1318 #ifdef  __hpux
1319     childPid = vfork();
1320 #else
1321     childPid = fork();
1322 #endif
1323     /*
1324      * if this is the child, exec the dthelpgen.ds script.
1325      */
1326     if (childPid == 0)
1327       {
1328         execlp("dthelpgen.ds", "dthelpgen.ds",
1329                                 ((char *) 0), ((char *) 0), ((char *) 0));
1330         _exit(1);
1331       }
1332
1333     /*
1334      * initialize the main topic
1335      */
1336     strcpy (TopicName, App_args.file);
1337     strcat (TopicName, "01.ht");
1338
1339     strcpy (headerName, App_args.file);
1340     strcat (headerName, "00.ht");
1341
1342     /*
1343      * The original dthelpgen extracts the CDE Standard name from
1344      * its message catalogue( set 2/msg 1 ).
1345      * But on IBM ODE, this is a problem. For example,
1346      * fr_FR's dthelpgen.cat has 
1347      *    fr_FR.ISO-8859-1 in set 2/msg 1.
1348      * Correct Fr_FR's message catalogue must have,
1349      *    fr_FR.IBM-850
1350      * there. But current IBM ode's Makefile cannot do this. Instead put
1351      *    fr_FR.ISO-8859-1. ( That is "do nothing" ).
1352      * To fix this, dthelpgen converts the current IBM LANG to CDE
1353      * standard name with _DtLcx*() function provided by libDtHelp.a as
1354      * internal API.
1355      */
1356 #ifdef _AIX
1357     {
1358         _DtXlateDb db = NULL;
1359         int  ret;
1360         char plat[_DtPLATFORM_MAX_LEN];
1361         int  execver;
1362         int  compver;
1363         char *ret_stdLocale;
1364         char *ret_stdLangTerr;
1365         char *ret_stdCodeset;
1366         char *ret_stdModifier;
1367
1368         ret = _DtLcxOpenAllDbs( &db );
1369         if ( !ret ) {
1370             ret = _DtXlateGetXlateEnv( db, plat, &execver, &compver );
1371             if ( !ret ) {
1372                 ret =  _DtLcxXlateOpToStd( db, plat, execver,
1373                                         DtLCX_OPER_SETLOCALE,
1374                                         setlocale( LC_MESSAGES, NULL ),
1375                                         &ret_stdLocale, &ret_stdLangTerr,
1376                                         &ret_stdCodeset, &ret_stdModifier );
1377                 if ( !ret ) {
1378                     charSet = strdup( ret_stdLocale );
1379                 } else {
1380                     charSet = "C.ISO-8859-1";
1381                 }
1382             } else {
1383                 charSet = "C.ISO-8859-1";
1384             }
1385             ret = _DtLcxCloseDb( &db );
1386         } else {
1387             charSet = "C.ISO-8859-1";
1388         }
1389
1390     }
1391 #else /* _AIX */
1392     charSet    = strdup (GetMessage (2, 1, "C.ISO-8859-1"));
1393 #endif /* _AIX */
1394     topicTitle = strdup (GetMessage (2, 2, "Welcome to Help Manager"));
1395
1396     fprintf (outHeader, (GetMessage (3, 1, (char*)defaultTopic)),
1397                                                 charSet, topicTitle);
1398
1399     fprintf (outHeader, (GetMessage (3, 2, (char*)defaultTitle14)), topicTitle);
1400     free(topicTitle);
1401
1402     preamble = ftell (outHeader);
1403     fprintf (outHeader, "%s\n", GetMessage (2, 3, (char*)defaultTextBody));
1404
1405     /*
1406      * loop through the directories looking for all the unique families
1407      * and -all- the volumes.
1408      */
1409     fprintf (outVolume, "!# Last modification time stamp per directory\n");
1410     next = FUserList;
1411     while (next != NULL && *next != NULL)
1412       {
1413         ScanDirectory(*next, &modTime);
1414         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1415         next++;
1416       }
1417     next = FSysList;
1418     while (next != NULL && *next != NULL)
1419       {
1420         ScanDirectory(*next, &modTime);
1421         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1422         next++;
1423       }
1424
1425     next = VUserList;
1426     while (next != NULL && *next != NULL)
1427       {
1428         ScanDirectory(*next, &modTime);
1429         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1430         next++;
1431       }
1432     next = VSysList;
1433     while (next != NULL && *next != NULL)
1434       {
1435         ScanDirectory(*next, &modTime);
1436         fprintf (outVolume, "*.%s.timeStamp:   %ld\n" , *next, modTime);
1437         next++;
1438       }
1439
1440     fprintf (outVolume, "*.charSet:     %s\n", charSet);
1441     free(charSet);
1442     fprintf (outVolume, "\n!# Topic filenames and offsets\n");
1443
1444     /*
1445      * Now create families.
1446      */
1447     foundVolumes = 0;
1448     foundFamily  = 0;
1449     for (next = FullFamilyName; next != NULL && *next != NULL; next++)
1450       {
1451         result = CreateFamily(canvasHandle,*next,outVolume,outHeader,outTopic);
1452         if (result == 0)
1453           {
1454             FamilyNum++;
1455             foundVolumes = 1;
1456             foundFamily  = 1;
1457           }
1458         else if (result == -2)
1459             foundFamily  = 1;
1460       }
1461
1462     if (foundFamily == 0)
1463         fprintf(stderr,
1464                 GetMessage(1, 16, "%s: Zero Family files found\n"), myName);
1465     else if (foundVolumes == 0)
1466         fprintf (stderr,
1467                 GetMessage (1, 17, "%s: Zero Volume files found\n"), myName);
1468
1469     /*
1470      * Clean up
1471      */
1472     if (FamilyList != NULL)
1473         _DtHelpCeFreeStringArray(FamilyList);
1474     if (FullFamilyName != NULL)
1475         _DtHelpCeFreeStringArray(FullFamilyName);
1476     if (VolumeList != NULL)
1477         _DtHelpCeFreeStringArray(VolumeList);
1478     if (FullVolName != NULL)
1479         _DtHelpCeFreeStringArray(FullVolName);
1480
1481     /*
1482      * If no family or volume files were found,
1483      * write out the alternative preamble
1484      */
1485     if (foundFamily == 0 || foundVolumes == 0)
1486       {
1487         fseek (outHeader, preamble, 0);
1488         fprintf (outHeader, "%s\n", GetMessage (2, 5, (char*)defaultAlternate));
1489       }
1490
1491     /*
1492      * write the ending message and finish the topic.
1493      */
1494     fprintf (outHeader, "</TOPIC>\n");
1495
1496     /*
1497      * write out the volume resouces
1498      */
1499     fprintf (outVolume, "\n\n!# Top (or home) topic filename and offset\n");
1500     fprintf (outVolume, "*.%s.filepos:   0\n" , ParentName);
1501     fprintf (outVolume, "*.%s.filename:  %s\n", ParentName, headerName);
1502
1503     fprintf (outVolume, "\n\n!# Volume Title\n");
1504     fprintf (outVolume, "*.title:     %s\n",
1505                       GetMessage (2, 4, "Help - Top Level"));
1506
1507     /*
1508      * top topic
1509      */
1510     fprintf (outVolume, "\n\n!# Topic Home Location\n");
1511     fprintf (outVolume, "*.topTopic:  %s\n", ParentName);
1512
1513     /*
1514      * topic heirarchy
1515      */
1516     fprintf (outVolume, "\n\n!# Topic Heirarchy\n");
1517     fprintf (outVolume, "*.%s.parent:  \n", ParentName);
1518     for (next = TopicList; next && *next; next++)
1519         fprintf (outVolume, "*.%s.parent: %s\n", *next, ParentName);
1520
1521     /*
1522      * topic list
1523      */
1524     fprintf (outVolume, "\n\n!# Topic List\n");
1525     fprintf (outVolume, "*.topicList: %s", ParentName);
1526     next = TopicList;
1527     while (next && *next)
1528       {
1529         fprintf (outVolume, " \\\n      %s", *next);
1530         next++;
1531       }
1532     fprintf (outVolume, "\n");
1533
1534     /*
1535      * The paths used to create this information.
1536      */
1537     fprintf (outVolume, "\n\n!# Paths Searched\n");
1538     fprintf (outVolume, "*.dirList: ");
1539
1540     next = FUserList;
1541     while (next != NULL && *next != NULL)
1542       {
1543         fprintf (outVolume, " \\\n      %s", *next);
1544         next++;
1545       }
1546     next = FSysList;
1547     while (next != NULL && *next != NULL)
1548       {
1549         fprintf (outVolume, " \\\n      %s", *next);
1550         next++;
1551       }
1552     next = VUserList;
1553     while (next != NULL && *next != NULL)
1554       {
1555         fprintf (outVolume, " \\\n      %s", *next);
1556         next++;
1557       }
1558     next = VSysList;
1559     while (next != NULL && *next != NULL)
1560       {
1561         fprintf (outVolume, " \\\n      %s", *next);
1562         next++;
1563       }
1564     fprintf (outVolume, "\n");
1565
1566     /*
1567      * close the volumes.
1568      */
1569     fclose (outVolume);
1570     fclose (outHeader);
1571     fclose (outTopic);
1572
1573     if (TopicList != NULL)
1574         _DtHelpCeFreeStringArray(TopicList);
1575
1576     if (FUserList != NULL)
1577         _DtHelpCeFreeStringArray(FUserList);
1578     if (FSysList != NULL)
1579         _DtHelpCeFreeStringArray(FSysList);
1580     if (VUserList != NULL)
1581         _DtHelpCeFreeStringArray(VUserList);
1582     if (VSysList != NULL)
1583         _DtHelpCeFreeStringArray(VSysList);
1584
1585     _DtHelpCeDestroyCanvas(canvasHandle);
1586
1587     MyExit (0, childPid);
1588 }