Logo Search packages:      
Sourcecode: nedit version File versions  Download package

fileUtils.c

static const char CVSID[] = "$Id: fileUtils.c,v 1.35 2008/08/20 14:57:35 lebert Exp $";
/*******************************************************************************
*                                                            *
* fileUtils.c -- File utilities for Nirvana applications                 *
*                                                            *
* Copyright (C) 1999 Mark Edel                                           *
*                                                            *
* This is free software; you can redistribute it and/or modify it under the    *
* terms of the GNU General Public License as published by the Free Software    *
* Foundation; either version 2 of the License, or (at your option) any later   *
* version. In addition, you may distribute versions of this program linked to  *
* Motif or Open Motif. See README for details.                                 *
*                                                                              *
* This software is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License        *
* for more details.                                                *
*                                                            *
* You should have received a copy of the GNU General Public License along with *
* software; if not, write to the Free Software Foundation, Inc., 59 Temple     *
* Place, Suite 330, Boston, MA  02111-1307 USA                               *
*                                                            *
* Nirvana Text Editor                                              *
* July 28, 1992                                                    *
*                                                            *
* Written by Mark Edel                                             *
*                                                            *
* Modified by:    DMR - Ported to VMS (1st stage for Histo-Scope)              *
*                                                            *
*******************************************************************************/

#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif

#include "fileUtils.h"
#include "utils.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <X11/Intrinsic.h>
#ifdef VAXC
#define NULL (void *) 0
#endif /*VAXC*/
#ifdef VMS
#include "vmsparam.h"
#include <stat.h>
#else
#include <sys/types.h>
#ifndef __MVS__
#include <sys/param.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#endif /*VMS*/

#ifdef HAVE_DEBUG_H
#include "../debug.h"
#endif

#ifndef MAXSYMLINKS  /* should be defined in <sys/param.h> */
#define MAXSYMLINKS 20
#endif

#define TRUE 1
#define FALSE 0

/* Parameters to algorithm used to auto-detect DOS format files.  NEdit will
   scan up to the lesser of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
   characters of the beginning of the file, checking that all newlines are
   paired with carriage returns.  If even a single counterexample exists,
   the file is judged to be in Unix format. */
#define FORMAT_SAMPLE_LINES 5
#define FORMAT_SAMPLE_CHARS 2000

static char *nextSlash(char *ptr);
static char *prevSlash(char *ptr);
static int compareThruSlash(const char *string1, const char *string2);
static void copyThruSlash(char **toString, char **fromString);

/*
** Decompose a Unix file name into a file name and a path.
** Return non-zero value if it fails, zero else.
** For now we assume that filename and pathname are at
** least MAXPATHLEN chars long.
** To skip setting filename or pathname pass NULL for that argument.
*/
int
ParseFilename(const char *fullname, char *filename, char *pathname)
{
    int fullLen = strlen(fullname);
    int i, pathLen, fileLen;
          
#ifdef VMS
    /* find the last ] or : */
    for (i=fullLen-1; i>=0; i--) {
      if (fullname[i] == ']' || fullname[i] == ':')
          break;
    }
#else  /* UNIX */
    char *viewExtendPath;
    int scanStart;
    
    /* For clearcase version extended paths, slash characters after the "@@/"
       should be considered part of the file name, rather than the path */
    if ((viewExtendPath = strstr(fullname, "@@/")) != NULL)
            scanStart = viewExtendPath - fullname - 1;
    else
            scanStart = fullLen - 1;
    
    /* find the last slash */
    for (i=scanStart; i>=0; i--) {
        if (fullname[i] == '/')
          break;
    }
#endif

    /* move chars before / (or ] or :) into pathname,& after into filename */
    pathLen = i + 1;
    fileLen = fullLen - pathLen;
    if (pathname) {
            if (pathLen >= MAXPATHLEN) {
            return 1;
      }
            strncpy(pathname, fullname, pathLen);
            pathname[pathLen] = 0;
    }
    if (filename) {
            if (fileLen >= MAXPATHLEN) {
                return 2;
            }
            strncpy(filename, &fullname[pathLen], fileLen);
            filename[fileLen] = 0;
    }

#ifndef VMS /* UNIX specific... Modify at a later date for VMS */
    if(pathname) {
      if (NormalizePathname(pathname)) {
          return 1; /* pathname too long */
      }
      pathLen = strlen(pathname);
    }
#endif
    
    if (filename && pathname && ((pathLen + 1 + fileLen) >= MAXPATHLEN)) {
      return 1; /* pathname + filename too long */
    }
    return 0;
}

#ifndef VMS


/*
** Expand tilde characters which begin file names as done by the shell
** If it doesn't work just out leave pathname unmodified.
** This implementation is neither fast, nor elegant, nor ...
*/
int
ExpandTilde(char *pathname)
{
    struct passwd *passwdEntry;
    char username[MAXPATHLEN], temp[MAXPATHLEN];
    char *nameEnd;
    unsigned len_left;
    
    if (pathname[0] != '~')
      return TRUE;
    nameEnd = strchr(&pathname[1], '/');
    if (nameEnd == NULL) {
      nameEnd = pathname + strlen(pathname);
    }
    strncpy(username, &pathname[1], nameEnd - &pathname[1]);
    username[nameEnd - &pathname[1]] = '\0';
    /* We might consider to re-use the GetHomeDir() function,
       but to keep the code more similar for both cases ... */
    if (username[0] == '\0') {
      passwdEntry = getpwuid(getuid());
      if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) {
         /* This is really serious, so just exit. */
           perror("NEdit/nc: getpwuid() failed ");
           exit(EXIT_FAILURE);
      }
    }
    else {
      passwdEntry = getpwnam(username);
        if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) {
           /* username was just an input by the user, this is no
            indication for some (serious) problems */
           return FALSE;
      }
    }

    strcpy(temp, passwdEntry->pw_dir);
    strcat(temp, "/");
    len_left= sizeof(temp)-strlen(temp)-1;
    if (len_left < strlen(nameEnd)) {
      /* It won't work out */
       return FALSE;
    }
    strcat(temp, nameEnd);
    strcpy(pathname, temp);
    return TRUE;
}

/*
 * Resolve symbolic links (if any) for the absolute path given in pathIn 
 * and place the resolved absolute path in pathResolved. 
 * -  pathIn must contain an absolute path spec.
 * -  pathResolved must point to a buffer of minimum size MAXPATHLEN.
 *
 * Returns:
 *   TRUE  if pathResolved contains a valid resolved path
 *         OR pathIn is not a symlink (pathResolved will have the same
 *          contents like pathIn)
 *
 *   FALSE an error occured while trying to resolve the symlink, i.e.
 *         pathIn was no absolute path or the link is a loop.
 */
int
ResolvePath(const char * pathIn, char * pathResolved) 
{
    char resolveBuf[MAXPATHLEN], pathBuf[MAXPATHLEN];
    char *pathEnd;
    int rlResult, loops;

#ifdef NO_READLINK
    strncpy(pathResolved, pathIn, MAXPATHLEN);
    /* If there are no links at all, it's a valid "resolved" path */
    return TRUE;
#else
    /* !! readlink does NOT recognize loops, i.e. links like file -> ./file */
    for (loops=0; loops<MAXSYMLINKS; loops++) {
#ifdef UNICOS
      rlResult=readlink((char *)pathIn, resolveBuf, MAXPATHLEN-1);
#else
      rlResult=readlink(pathIn, resolveBuf, MAXPATHLEN-1);
#endif
        if (rlResult<0) {

#ifndef __Lynx__
          if (errno == EINVAL)
#else
          if (errno == ENXIO)
#endif
            {
                /* It's not a symlink - we are done */
                strncpy(pathResolved, pathIn, MAXPATHLEN);
                pathResolved[MAXPATHLEN-1] = '\0';
                return TRUE;
            } else {
                return FALSE;
            }
        } else if (rlResult == 0) {
          return FALSE;
      }

      resolveBuf[rlResult]=0;

        if (resolveBuf[0]!='/') {
          strncpy(pathBuf, pathIn, MAXPATHLEN);
          pathBuf[MAXPATHLEN-1] = '\0';
          pathEnd=strrchr(pathBuf, '/');
            if (!pathEnd) {
                  return FALSE;
            }
          strcpy(pathEnd+1, resolveBuf);
      } else {
          strcpy(pathBuf, resolveBuf);
      }
      NormalizePathname(pathBuf);
      pathIn=pathBuf;
    }

    return FALSE;
#endif /* NO_READLINK */
}


/*
** Return 0 if everything's fine. In fact it always return 0... (No it doesn't)
** Capable to handle arbitrary path length (>MAXPATHLEN)!
**
**  FIXME: Documentation
**  FIXME: Change return value to False and True.
*/
int NormalizePathname(char *pathname)
{
    /* if this is a relative pathname, prepend current directory */
#ifdef __EMX__
    /* OS/2, ...: welcome to the world of drive letters ... */
    if (!_fnisabs(pathname)) {
#else
    if (pathname[0] != '/') {
#endif
        char *oldPathname;
        size_t len;

        /* make a copy of pathname to work from */
      oldPathname=(char *)malloc(strlen(pathname)+1);
      strcpy(oldPathname, pathname);
      /* get the working directory and prepend to the path */
      strcpy(pathname, GetCurrentDir());

      /* check for trailing slash, or pathname being root dir "/":
         don't add a second '/' character as this may break things
         on non-un*x systems */
        len = strlen(pathname); /* GetCurrentDir() returns non-NULL value */

        /*  Apart from the fact that people putting conditional expressions in
            ifs should be caned: How should len ever become 0 if GetCurrentDir()
            always returns a useful value?
            FIXME: Check and document GetCurrentDir() return behaviour.  */
        if (0 == len ? 1 : pathname[len-1] != '/')
        {
            strcat(pathname, "/");
        }
      strcat(pathname, oldPathname);
      free(oldPathname);
    }

    /* compress out .. and . */
    return CompressPathname(pathname);
}


/*
** Return 0 if everything's fine, 1 else.
**
**  FIXME: Documentation
**  FIXME: Change return value to False and True.
*/
int CompressPathname(char *pathname)
{
    char *buf, *inPtr, *outPtr;
    struct stat statbuf;

    /* (Added by schwarzenberg)
    ** replace multiple slashes by a single slash 
    **  (added by yooden)
    **  Except for the first slash. From the Single UNIX Spec: "A pathname
    **  that begins with two successive slashes may be interpreted in an
    **  implementation-dependent manner"
    */
    inPtr = pathname;
    buf = (char*) malloc(strlen(pathname) + 2);
    outPtr = buf;
    *outPtr++ = *inPtr++;
    while (*inPtr)
    {
        *outPtr = *inPtr++;
        if ('/' == *outPtr)
        {
            while ('/' == *inPtr)
            {
                inPtr++;
            }
        }
        outPtr++;
    }
    *outPtr=0;
    strcpy(pathname, buf);
    
    /* compress out . and .. */
    inPtr = pathname;
    outPtr = buf;
    /* copy initial / */
    copyThruSlash(&outPtr, &inPtr);
    while (inPtr != NULL) {
      /* if the next component is "../", remove previous component */
      if (compareThruSlash(inPtr, "../")) {
            *outPtr = 0;
          /* If the ../ is at the beginning, or if the previous component
             is a symbolic link, preserve the ../.  It is not valid to
             compress ../ when the previous component is a symbolic link
             because ../ is relative to where the link points.  If there's
             no S_ISLNK macro, assume system does not do symbolic links. */
#ifdef S_ISLNK
          if(outPtr-1 == buf || (lstat(buf, &statbuf) == 0 &&
                S_ISLNK(statbuf.st_mode))) {
            copyThruSlash(&outPtr, &inPtr);
          } else
#endif          
          {
            /* back up outPtr to remove last path name component */
            outPtr = prevSlash(outPtr);
            inPtr = nextSlash(inPtr);
          }
      } else if (compareThruSlash(inPtr, "./")) {
          /* don't copy the component if it's the redundant "./" */
          inPtr = nextSlash(inPtr);
      } else {
          /* copy the component to outPtr */
          copyThruSlash(&outPtr, &inPtr);
      }
    }
    /* updated pathname with the new value */
    if (strlen(buf)>MAXPATHLEN) {
       fprintf(stderr, "nedit: CompressPathname(): file name too long %s\n",
               pathname);
       free(buf);
       return 1;
    }
    else {
       strcpy(pathname, buf);
       free(buf);
       return 0;
    }
}

static char
*nextSlash(char *ptr)
{
    for(; *ptr!='/'; ptr++) {
      if (*ptr == '\0')
          return NULL;
    }
    return ptr + 1;
}

static char
*prevSlash(char *ptr)
{
    for(ptr -= 2; *ptr!='/'; ptr--);
    return ptr + 1;
}

static int
compareThruSlash(const char *string1, const char *string2)
{
    while (TRUE) {
      if (*string1 != *string2)
          return FALSE;
      if (*string1 =='\0' || *string1=='/')
          return TRUE;
      string1++;
      string2++;
    }
}

static void
copyThruSlash(char **toString, char **fromString)
{
    char *to = *toString;
    char *from = *fromString;
    
    while (TRUE) {
        *to = *from;
        if (*from =='\0') {
            *fromString = NULL;
            return;
        }
      if (*from=='/') {
          *toString = to + 1;
          *fromString = from + 1;
          return;
      }
      from++;
      to++;
    }
}

#else /* VMS */

/* 
** Dummy versions of the public functions for VMS.
*/

/*
** Return 0 if everything's fine, 1 else.
*/
int NormalizePathname(char *pathname)
{
    return 0;
}

/*
** Return 0 if everything's fine, 1 else.
*/
int CompressPathname(char *pathname)
{
    return 0;
}

/*
 * Returns:
 *   TRUE  if no error occured
 *
 *   FALSE if an error occured.
 */
int ResolvePath(const char * pathIn, char * pathResolved)
{
    if (strlen(pathIn) < MAXPATHLEN) {
      strcpy(pathResolved, pathIn);
      return TRUE;
    } else {
      return FALSE;
    }
}

#endif /* VMS */

/*
** Return the trailing 'n' no. of path components
*/
const char
*GetTrailingPathComponents(const char* path,
                                      int noOfComponents)
{
    /* Start from the rear */
    const char* ptr = path + strlen(path);
    int count = 0;

    while (--ptr > path) {
        if (*ptr == '/') {
            if (count++ == noOfComponents) {
                break;
            }
        }
    }
    return(ptr);
} 

/*
** Samples up to a maximum of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
** characters, to determine whether fileString represents a MS DOS or Macintosh
** format file.  If there's ANY ambiguity (a newline in the sample not paired
** with a return in an otherwise DOS looking file, or a newline appearing in
** the sampled portion of a Macintosh looking file), the file is judged to be
** Unix format.
*/
int FormatOfFile(const char *fileString)
{
    const char *p;
    int nNewlines = 0, nReturns = 0;
    
    for (p=fileString; *p!='\0' && p < fileString + FORMAT_SAMPLE_CHARS; p++) {
      if (*p == '\n') {
          nNewlines++;
          if (p == fileString || *(p-1) != '\r')
            return UNIX_FILE_FORMAT;
          if (nNewlines >= FORMAT_SAMPLE_LINES)
            return DOS_FILE_FORMAT;
      } else if (*p == '\r')
          nReturns++;
    }
    if (nNewlines > 0)
      return DOS_FILE_FORMAT;
    if (nReturns > 0)
      return MAC_FILE_FORMAT;
    return UNIX_FILE_FORMAT;
}

/*
** Converts a string (which may represent the entire contents of the file)
** from DOS or Macintosh format to Unix format.  Conversion is done in-place.
** In the DOS case, the length will be shorter, and passed length will be
** modified to reflect the new length. The routine has support for blockwise
** file to string conversion: if the fileString has a trailing '\r' and 
** 'pendingCR' is not zero, the '\r' is deposited in there and is not
** converted. If there is no trailing '\r', a 0 is deposited in 'pendingCR'
** It's the caller's responsability to make sure that the pending character, 
** if present, is inserted at the beginning of the next block to convert.
*/
void ConvertFromDosFileString(char *fileString, int *length, 
    char* pendingCR)
{
    char *outPtr = fileString;
    char *inPtr = fileString;
    if (pendingCR) *pendingCR = 0;
    while (inPtr < fileString + *length) {
      if (*inPtr == '\r') {
          if (inPtr < fileString + *length - 1) {
            if (*(inPtr + 1) == '\n')
                inPtr++;
          } else {
            if (pendingCR) {
                *pendingCR = *inPtr;
                break; /* Don't copy this trailing '\r' */
            }
          }
      }
      *outPtr++ = *inPtr++;
    }
    *outPtr = '\0';
    *length = outPtr - fileString;
}
void ConvertFromMacFileString(char *fileString, int length)
{
    char *inPtr = fileString;
    while (inPtr < fileString + length) {
        if (*inPtr == '\r' )
            *inPtr = '\n';
        inPtr++;
    }
}

/*
** Converts a string (which may represent the entire contents of the file) from
** Unix to DOS format.  String is re-allocated (with malloc), and length is
** modified.  If allocation fails, which it may, because this can potentially
** be a huge hunk of memory, returns FALSE and no conversion is done.
**
** This could be done more efficiently by asking doSave to allocate some
** extra memory for this, and only re-allocating if it wasn't enough.  If
** anyone cares about the performance or the potential for running out of
** memory on a save, it should probably be redone.
*/
int ConvertToDosFileString(char **fileString, int *length)
{
    char *outPtr, *outString;
    char *inPtr = *fileString;
    int inLength = *length;
    int outLength = 0;

    /* How long a string will we need? */
    while (inPtr < *fileString + inLength) {
      if (*inPtr == '\n')
          outLength++;
      inPtr++;
      outLength++;
    }
    
    /* Allocate the new string */
    outString = XtMalloc(outLength + 1);
    if (outString == NULL)
      return FALSE;
    
    /* Do the conversion, free the old string */
    inPtr = *fileString;
    outPtr = outString;
    while (inPtr < *fileString + inLength) {
      if (*inPtr == '\n')
          *outPtr++ = '\r';
      *outPtr++ = *inPtr++;
    }
    *outPtr = '\0';
    XtFree(*fileString);
    *fileString = outString;
    *length = outLength;
    return TRUE;
}

/*
** Converts a string (which may represent the entire contents of the file)
** from Unix to Macintosh format.
*/
void ConvertToMacFileString(char *fileString, int length)
{
    char *inPtr = fileString;
    
    while (inPtr < fileString + length) {
      if (*inPtr == '\n' )
            *inPtr = '\r';
      inPtr++;
    }
}

/*
** Reads a text file into a string buffer, converting line breaks to 
** unix-style if appropriate. 
**
** Force a terminating \n, if this is requested
*/
char *ReadAnyTextFile(const char *fileName, int forceNL)
{
    struct stat statbuf;
    FILE *fp;
    int fileLen, readLen;
    char *fileString;
    int format;
            
    /* Read the whole file into fileString */
    if ((fp = fopen(fileName, "r")) == NULL) {
      return NULL;
    }
    if (fstat(fileno(fp), &statbuf) != 0) {
      fclose(fp);
      return NULL;
    }
    fileLen = statbuf.st_size;
    /* +1 = space for null
    ** +1 = possible additional \n
    */
    fileString = XtMalloc(fileLen + 2);
    readLen = fread(fileString, sizeof(char), fileLen, fp);
    if (ferror(fp)) {
      XtFree(fileString);
      fclose(fp);
      return NULL;
    }
    fclose(fp);
    fileString[readLen] = 0;

    /* Convert linebreaks? */
    format = FormatOfFile(fileString);
    if (format == DOS_FILE_FORMAT){
        char pendingCR;
        ConvertFromDosFileString(fileString, &readLen, &pendingCR);
    } else if (format == MAC_FILE_FORMAT){
        ConvertFromMacFileString(fileString, readLen);
    }

    /* now, that the fileString is in Unix format, check for terminating \n */
    if (forceNL && fileString[readLen - 1] != '\n') {
        fileString[readLen]     = '\n';
        fileString[readLen + 1] = '\0';
    }

    return fileString;
}

Generated by  Doxygen 1.6.0   Back to index