/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $TOG: FileUtils.c /main/8 1998/07/28 15:37:38 mgreess $ */
/************************************<+>*************************************
 ****************************************************************************
 **
 **   File:	FileUtils.c
 **
 **   Project:     File locating
 **
 **   Description: Locates files (volumes) accessible via the 
 **             known paths
 **
 **   NOTE: this file must remain free of Xt & Xm calls.
 **
 **  (c) Copyright 1993, 1994 Hewlett-Packard Company
 **  (c) Copyright 1993, 1994 International Business Machines Corp.
 **  (c) Copyright 1993, 1994 Sun Microsystems, Inc.
 **  (c) Copyright 1993, 1994 Novell, Inc.
 ****************************************************************************
 ************************************<+>*************************************/

/*
 * system includes
 */
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/param.h> /* MAXPATHLEN */

#include <Dt/Help.h>

/*
 * private includes
 */
#include "bufioI.h"  /* for AccessI.h */
#include "Access.h"     /* CompressPathname */
#include "AccessI.h"    /* ExpandPathname */
#include "HelpP.h"      /* for DtDEFAULT_xxx */
#include "HelposI.h"    /* for search path access */
#include "StringFuncsI.h"
#include "FileUtilsI.h"
#include "Lock.h"

/******** constants *********/
#define LANG_C_STR     "C"
#define DIR_SLASH      '/'
#define EOS            '\0'

/******** types *********/

/******** public global variables *********/
const char * _DtHelpFileSuffixList[3] = {
                    DtHelpSDL_VOL_SUFFIX,
                    DtHelpCCDF_VOL_SUFFIX,
                    NULL };

/******** variables *********/
#ifdef	not_used
static const char * PeriodStr = ".";
#endif
static const char * DirSlashStr = "/";
static const char * PathSeparator = ":";
static const _DtHelpCeDirStruct DefCacheDir = { NULL, ENOTDIR, 0, NULL };

static	_DtHelpCeDirStruct *CachedDirs = NULL;

/******** functions *********/
#define MyNewString(s) (NULL != s ? strdup((char *)s) : NULL)
#define MyRealloc(p,s) (NULL != (p) ? realloc((char *)p,s) : malloc(s) )
#define MyMalloc(s)    malloc(s)


/*****************************************************************************
 * Function:   MyFree()
 *
 *  locate the '.' of the filename extension, if present
 *
 *****************************************************************************/
static void MyFree(char * ptr)
{
   if(ptr) free(ptr);
}


#ifdef	not_used
/*****************************************************************************
 * Function:   GetExtension()
 *
 *  locate the '.' of the filename extension, if present
 *
 *****************************************************************************/
static char * GetExtension(
            char *         filename)
{
    char * ext;
    if (_DtHelpCeStrrchr(filename,PeriodStr,MB_CUR_MAX,&ext) == 0 ) return ext;
    else return "";          /* do NOT return a NULL*/
}



/*****************************************************************************
 * Function:   SpecialStrcmp()
 *
 * Tests the args for NULL pointers. If both are NULL or if
 * both aren't NULL and are the same string, then returns 0.
 * If one arg is NULL and other isn't, or if strings are
 * different, returns -1 or +1.
 *
 *****************************************************************************/
static int SpecialStrcmp(
    const char *    str1,
    const char *    str2)
{
           if(NULL == str1)
           {
              if(NULL == str2) return 0;   /* str1 == str2 */
              return -1;                   /* str1 < str2 */
           }
           if(NULL == str2) return 1;      /* str1 > str2 */
           return(strcmp(str1,str2));      /* str1 ? str2 */
}
#endif




/************************************************************************
 * Function: _DtHelpFileTraceLinks (pathName)
 *
 * Purpose:  Traces pathname through all symbolic links
 *           until a real file is found or the link is
 *           found to be invalid.
 *
 * Returns:  True if file found at end of link, False if not
 *
 * Memory:  pathName must point to a malloc'd string.  The string
 *          may be freed by the function and pathName assigned a pointer
 *          to a different string specifying a different path to the same file.
 *          If a file is found, foundPath is set to the same pointer
 *          as pathName, otherwise it is set to NULL.
 ***********************************************************************/
Boolean 
_DtHelpFileTraceLinks (
             char * *  pPathName)
{
   int    result = 0;
   char   curBuf;
   char * linkPath;
   char * filePath;
   char   buf [2][MAXPATHLEN+2];      /* 2K+ bytes on stack */

   if ( NULL == *pPathName ) return False;         /* RETURN */

   /* init */
   snprintf(buf[0], sizeof(buf[0]), "%s", *pPathName);
   linkPath = buf[0];        /* will be assigned to filePath below */
   curBuf = 1;               /* next valid buf */

   /* find out if this path is a symbolic link */
   while ( result >= 0 )
   {
      /* exchange buffer ptrs and toggle index */
      filePath = linkPath;
      linkPath = buf[curBuf % 2];
      curBuf++;

      /* get the link info */
      result = readlink (filePath, linkPath, MAXPATHLEN);

     /* check for the result of the readlink call */
     if (result == -1)
     {
        /* if newPath is not a symbolic link, errno != EINVAL */
        if (errno != EINVAL)
           return False;                    /* RETURN */

        /* filePath is not a sym link ==> a real file or directory */
        /* so return filePath in caller-owned memory */
        if ( curBuf != 1 )  /* curBuf == 1 when pPathName is a file */
        {
	   /*
	    * pPathName had memory allocated before this function was called.
	    * only increase the memory if needed.
	    */
           if ( strlen (*pPathName) < strlen(filePath) )
                *pPathName = (char *)realloc((void *)*pPathName, (sizeof(char)
                                * (strlen(filePath) +1)));

           strcpy(*pPathName, filePath);
        }
        /* printf("actual is: %s\n", filePath);  ** DBG */
        return True;                        /* RETURN */
     }  /* if an error */
     else
     {  /* no error--handle the link */

        /* if the path is absolute, just take it as such */
        linkPath [result] = EOS;       /* for safety */

        /* is path relative to current directory? */
        if ( linkPath[0] != DIR_SLASH )
        {
           char * slash = NULL;

           /* get last slash in the current file path */
           if(_DtHelpCeStrrchr(filePath,DirSlashStr,MB_CUR_MAX,&slash) == 0)
           { /* there is a path component in filePath; use it with linkPath */
              strcpy(++slash,linkPath);
              strcpy(linkPath,filePath);       /* leave result in linkPath */
           }
        } /* if path is relative */
        /* printf("traced to: %s\n", linkPath);  ** DBG */
     }  /* if no error */
   }  /* while result >= 0 */
   return False;                             /* RETURN */
}




/************************************************************************
 * Function: _DtHelpFileTraceToFile (pathName, accessMode, foundPath)
 *
 * Memory:  pPathName must point to a malloc'd string.  The string
 *          may be freed by the function and pathName assigned a pointer
 *          to a different string specifying a different path to the same file.
 *          If a file is found, foundPath is set to the same pointer
 *          as pathName, otherwise it is set to NULL.
 * Returns:
 *   True:  file found
 *   False: file not found or error
 ***********************************************************************/
Boolean 
_DtHelpFileTraceToFile (
             char * *  pPathName,
             int       accessMode,
             char * *  pFoundPath)
{
   struct stat status;
   char * pathName = *pPathName;  /* avoid indirection */

   *pFoundPath = NULL;
   if ( pathName == NULL || pathName[0] == EOS )
      return False;
 
   /* if it's a file, trace its links */
   if (   access (pathName, accessMode) == 0 
       && stat (pathName, &status) == 0
       && S_ISREG(status.st_mode) )    /* a file */
   {
      /* trace any links */
      if ( _DtHelpFileTraceLinks(pPathName) == False ) 
      {
          /* don't free pPathName here */
          return False;                           /* RETURN: no file */
      }
      pathName = *pPathName;
      
      /* find out if its an accessible file */
      if (   pathName != NULL 
          && pathName[0] != EOS
          && access (pathName, accessMode) == 0 
          && stat (pathName, &status) == 0
  	           && S_ISREG(status.st_mode))    /* a file */
      {
         /* point foundPath at the path */
         *pFoundPath = pathName;     
         return True;               /* RETURN:  its a file */
      }  /* if a valid path */
   } /* if a path */

#if 0
   printf("Unknown file: %s\n", pathName);
   printf("Access: %d, stat: %d, IS_REG: %d, mode: %x\n",
             access (pathName, accessMode),
             stat (pathName, &status),
             S_ISREG(status.st_mode),
             status.st_mode);
#endif

   /* its not a file */
   *pFoundPath = NULL;
   return False;
}




/******************************************************************************
 * Function: int _DtHelpFileGetSearchPaths ()
 *
 * Parameters:
 *        paths:          caller array size _DtHELP_FILE_NUM_PATHS in which
 *                        to store ptrs to the private path strings
 *        searchHomeDir:  boolean flag
 *
 * Memory:
 *        the memory pointed to by the array is NOT owned by the
 *        caller and should not be freed or modified
 *
 * Purpose:	make the search paths available
 * 
 *****************************************************************************/
void _DtHelpFileGetSearchPaths(
         char *  paths[],
         Boolean searchHomeDir)
{
static char * pathsSet[_DtHELP_FILE_NUM_PATHS];
    char tmpPath[MAXPATHLEN + 2];

    /* get user's home directory; is used in _DtHELP_FILE_USER_PATH as well */
    if (NULL == pathsSet[_DtHELP_FILE_HOME_PATH])
    {
       _DtHelpOSGetHomeDirName(tmpPath, sizeof(tmpPath));
       pathsSet[_DtHELP_FILE_HOME_PATH] = strdup(tmpPath);
    }
    if (searchHomeDir) 
       paths[_DtHELP_FILE_HOME_PATH] = pathsSet[_DtHELP_FILE_HOME_PATH];
    else 
       paths[_DtHELP_FILE_HOME_PATH] = NULL;

    /* generate the user path */
    if (NULL == pathsSet[_DtHELP_FILE_USER_PATH])
       pathsSet[_DtHELP_FILE_USER_PATH] = _DtHelpGetUserSearchPath();
    paths[_DtHELP_FILE_USER_PATH] = pathsSet[_DtHELP_FILE_USER_PATH];

    /* get the system search path */
    if (NULL == pathsSet[_DtHELP_FILE_SYS_PATH])
       pathsSet[_DtHELP_FILE_SYS_PATH] = _DtHelpGetSystemSearchPath();
    paths[_DtHELP_FILE_SYS_PATH] = pathsSet[_DtHELP_FILE_SYS_PATH];
}



/******************************************************************************
 * Function: char * _DtHelpFileLocate ()
 *
 * Parameters:
 *        type:           subdirectories to search (%T)
 *        base:           basename of the file
 *        suffix:         extension of the file to find (%S)
 *        searchCurDir:   boolean flag
 *        accessMode:     constant value from access(2)
 *
 * Returns:	malloc'd path of the located file or NULL if none located
 *
 * errno Values:
 *		EINVAL
 *
 * Purpose:	Scans all paths of given type looking for a matching file
 *              If file contains a valid absolute path, that is also
 *              acceptable.
 * 
 * FIX: merge _DtHelpFileLocate() and _DtHelpFileListScanPaths()
 *****************************************************************************/
char * _DtHelpFileLocate (
	char *        type,
        char *        filespec,
        const char *  suffixList[],
        Boolean       searchCurDir,
        int           accessMode)
{
    char * loc;
    char * ptr;
    char * pathName;
    char * curPath;
    char * base;
    int    curPathIndex;
    char * foundPath;
    const char empty = 0;
    const char * sufList[2];
#define NUM_BUGS 1
    _DtSubstitutionRec bugFixSubs [NUM_BUGS];
    char * paths[_DtHELP_FILE_NUM_PATHS];
    char tmpPath[MAXPATHLEN + 2];
    const char * * pSuffix;
    char * eos = NULL;
    char * slash = NULL;

    /* test args */
    if (NULL == filespec) return NULL;

    /* init suffix list to empty if not specified */
    if (suffixList == NULL) 
    { 
       sufList[0] = &empty; 
       sufList[1] = NULL;
       suffixList = sufList; /* override initial argument setting */
    }

    /*** first look for file as specified ***/
    /* if filespec begins with . or .. then stop after the cwd path */
    if (   (   MB_CUR_MAX == 1 
            || mblen(filespec, MB_CUR_MAX) == 1)	/* 1st char is 1 byte */
         && *filespec == '/')  			/* and its a / */
    {
       /* _DtHelpFileTraceToFile() needs a malloc'd string */
       /* 10: leaves room for add'l suffixes */
       pathName = MyMalloc(sizeof(char) * (strlen(filespec)+10)); 
       pathName = strcpy(pathName,filespec);
       _DtHelpCeCompressPathname(pathName); /* compress out relative paths */
       if ( _DtHelpFileTraceToFile(&pathName,accessMode,&foundPath) )
          return foundPath;                         /* RETURN */

       /* test all suffixes */
       eos = pathName + strlen(pathName);
       for ( pSuffix = suffixList; NULL != *pSuffix; pSuffix++ )
       {
          strcpy(eos,(char *) *pSuffix);
          /*recall: _DtHelpFileTraceToFile() requires pathName to be malloc'd*/
          if ( _DtHelpFileTraceToFile(&pathName,accessMode,&foundPath) )
             return foundPath;                           /* RETURN: found */
       } /* for all suffixes */

       MyFree(pathName);
    }

    /*** second, check if its relative to the current directory ***/
    /* if filespec begins with . or .. then stop after the cwd path */
    if (    searchCurDir
         || (      MB_CUR_MAX == 1 
                || mblen(filespec, MB_CUR_MAX) == 1)  /* 1st char is 1 byte */
             && *filespec == '.')  		      /* and its a . */
    {     /* we're looking at a cwd-relative path; ignore others */
       /*** this is monstrously inefficient--but it shouldn't get called often ***/

       /* get user's current working directory */
       /* JET - CERT VU#575804 */
       if (getcwd(tmpPath, MAXPATHLEN - 1) == NULL) return NULL; /* RETURN: error */
       
       /* make path end in a slash */
       eos = tmpPath + strlen(tmpPath);
       _DtHelpCeStrrchr(tmpPath,DirSlashStr,MB_CUR_MAX,&slash);
       if ( slash != (eos - 1) ) { *eos++ = DIR_SLASH; *eos = EOS; }

       /* make a malloc'd copy of the path with room to grow */
       slash = filespec + strlen(filespec);
       pathName = malloc(sizeof(char) * 
                    ((eos-tmpPath) + (slash-filespec) + 50) ); /* 50: arbitrary */
       if (NULL == pathName) return NULL;		/* RETURN: error */
       strcpy(pathName,tmpPath);

       /* cat on the relative path */
       strcat(pathName,filespec);

       /* compress out any relative paths */
       _DtHelpCeCompressPathname(pathName);

       /* see if we find the file now */
       /* recall: _DtHelpFileTraceToFile() requires pathName to be malloc'd */
       if ( _DtHelpFileTraceToFile(&pathName,accessMode,&foundPath) )
          return foundPath;                             /* RETURN: found */

       /* test all suffixes */
       eos = pathName + strlen(pathName);
       for ( pSuffix = suffixList; NULL != *pSuffix; pSuffix++ )
       {
         strcpy(eos,(char *) *pSuffix);
         /* recall: _DtHelpFileTraceToFile() requires pathName to be malloc'd */
         if ( _DtHelpFileTraceToFile(&pathName,accessMode,&foundPath) )
            return foundPath;                           /* RETURN: found */
       } /* for all suffixes */
       MyFree(pathName);
       return NULL;					/* RETURN: error */
    }  /* filespec is a relative path or search cur dir */

    /*** third look in search path directories ***/

    /* get the search paths */
    _DtHelpFileGetSearchPaths( paths, False );

    /*** prep variables to pass through the path search loop ***/
    /* we're not looking at a cwd-relative path and
       we know that 'filespec' isn't a valid path to a volume
       (from _DtHelpFileTraceToFile), so just pick off the 
       basename of the spec */
    base = filespec;
    if ( _DtHelpCeStrrchr(filespec,DirSlashStr,MB_CUR_MAX,&ptr) == 0 )
        base = ++ptr;      /* begin past the slash */

    /* Have to support %H explicitly */
    bugFixSubs[0].match = 'H';
    bugFixSubs[0].substitution = base;

    /* get the LANG value */
    loc = _DtHelpGetLocale();
    if (NULL == loc || EOS == loc[0]) loc = strdup(LANG_C_STR);

    /* outer loop is once for each path */
    foundPath = NULL;
    for ( curPathIndex = 0;
          curPathIndex < _DtHELP_FILE_NUM_PATHS && NULL == foundPath; 
          curPathIndex++ )
    {
       curPath = paths[curPathIndex];
       if (NULL == curPath) continue;			/* continue */
    
       /* look for the file in that path */
       if (NULL != curPath) do
       {
          /* look for next subpath separator and insert and EOS if found */
	  if (_DtHelpCeStrchr(curPath,PathSeparator,MB_CUR_MAX,&ptr)==0)
	      *ptr = EOS;

          /* compress that path */
          /* JET - CERT VU#575804 */
          strncpy(tmpPath, curPath, MAXPATHLEN);

          _DtHelpCeCompressPathname(tmpPath);

          /* test all suffixes */
          for ( pSuffix = suffixList, foundPath = NULL;
                NULL == foundPath && NULL != *pSuffix; 
                pSuffix++ )
          {
             /* generate the (directory) path using all the variables and fix 
                it up to remove the unwanted stuff involving the filename */
             pathName = _DtHelpCeExpandPathname (curPath, base, type, 
                            (char *) *pSuffix, loc, bugFixSubs, NUM_BUGS);
  
             if (   _DtHelpFileTraceToFile(&pathName,accessMode,&foundPath)==False
                 && NULL != pathName)
                free(pathName);

          } /* for all suffixes */
 
          /* restore the subpath separator and advance past it */
          if (ptr) *ptr++ = *PathSeparator;

	  curPath = ptr;
       } while (curPath && *curPath && NULL == foundPath); 
        /* do while more subpaths */

   }  /* for all paths */

   MyFree(loc);
   return foundPath;
}

/******************************************************************************
 * Function: int _DtHelpCeCheckAndCacheDir (char *dir)
 *
 * Parameters:
 *        dir	Specifies the directory to test.
 *
 * Returns:	0	if the directory exists.
 *		ENOTDIR	if the directory is invalid.
 *
 * Purpose:	To check a directory only once and remember the result.
 * 
 *****************************************************************************/
int
_DtHelpCeCheckAndCacheDir (char *dir)
{
    int		         result  = ENOTDIR;
    _DtHelpCeDirStruct	*curDir  = CachedDirs;
    _DtHelpCeDirStruct	*prevDir = NULL;
    struct stat		 buf;

    _DtHelpProcessLock();

    if (dir == NULL || *dir == '\0')
	return ENOTDIR;

    /*
     * search the cached directories
     */
    while (curDir != NULL && strcmp(curDir->dir_name, dir))
      {
	prevDir = curDir;
	curDir  = curDir->next_dir;
      }

    /*
     * was the directory found in the cache? If so, return the type.
     */
    if (curDir != NULL)
	result = curDir->type;
    else
      {
	/*
	 * new directory - malloc room for this entry.
	 */
	result = ENOMEM;
	curDir = (_DtHelpCeDirStruct *) malloc(sizeof(_DtHelpCeDirStruct));
	if (curDir != NULL)
	  {
	    /*
	     * initialize the new entry. I.E. type starts out ENOTDIR.
	     */
	    *curDir = DefCacheDir;
	    curDir->dir_name = strdup(dir);
	    if (curDir->dir_name != NULL)
	      {
		/*
		 * put this entry in the list
		 */
		if (prevDir != NULL)
		    prevDir->next_dir = curDir;
		else
		    CachedDirs = curDir;

		/*
		 * is this a directory?
		 */
		if (access(dir, R_OK) == 0 && 
				stat(dir, &buf) == 0 && S_ISDIR(buf.st_mode))
		    curDir->type = 0;

		/*
		 * return the result of the tests.
		 */
		result = curDir->type;
	      }
	    else
	        free(curDir);
	  }
      }

    /*
     * This should never happen, but just in case the directory
     * can't be cached, go ahead and check it anyway.
     */
    if (result == ENOMEM && access(dir, R_OK) == 0 && 
				stat(dir, &buf) == 0 && S_ISDIR(buf.st_mode))
	result = 0;

    _DtHelpProcessUnlock();
    return result;
}

#ifdef	not_done
/******************************************************************************
 * Function: _DtHelpCeDirStruct *_DtHelpCeGetCachedDirs (void)
 *
 * Parameters:	none.
 *
 * Returns:	A pointer to the cached directories.
 *
 * Purpose:	To allow access to the cached directories.
 * 
 *****************************************************************************/
_DtHelpCeDirStruct *
_DtHelpCeGetCachedDirs (void)
{
    return CachedDirs;
}
#endif /* not_done */
