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

utils.c

/*
 * Copyright (c) 2002 by David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * Utility routines.
 */

#include "sash.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <utime.h>


/*
 * A chunk of data.
 * Chunks contain data which is allocated as needed, but which is
 * not freed until all of the data needs freeing, such as at
 * the beginning of the next command.
 */
typedef     struct      chunk CHUNK;
#define     CHUNK_INIT_SIZE   4

00026 struct      chunk
{
      CHUNK *     next;
      char  data[CHUNK_INIT_SIZE];  /* actually of varying length */
};


static      CHUNK *     chunkList;



/*
 * Return the standard ls-like mode string from a file mode.
 * This is static and so is overwritten on each call.
 */
const char *
modeString(int mode)
{
      static      char  buf[12];

      strcpy(buf, "----------");

      /*
       * Fill in the file type.
       */
      if (S_ISDIR(mode))
            buf[0] = 'd';
      if (S_ISCHR(mode))
            buf[0] = 'c';
      if (S_ISBLK(mode))
            buf[0] = 'b';
      if (S_ISFIFO(mode))
            buf[0] = 'p';
#ifdef      S_ISLNK
      if (S_ISLNK(mode))
            buf[0] = 'l';
#endif
#ifdef      S_ISSOCK
      if (S_ISSOCK(mode))
            buf[0] = 's';
#endif

      /*
       * Now fill in the normal file permissions.
       */
      if (mode & S_IRUSR)
            buf[1] = 'r';
      if (mode & S_IWUSR)
            buf[2] = 'w';
      if (mode & S_IXUSR)
            buf[3] = 'x';
      if (mode & S_IRGRP)
            buf[4] = 'r';
      if (mode & S_IWGRP)
            buf[5] = 'w';
      if (mode & S_IXGRP)
            buf[6] = 'x';
      if (mode & S_IROTH)
            buf[7] = 'r';
      if (mode & S_IWOTH)
            buf[8] = 'w';
      if (mode & S_IXOTH)
            buf[9] = 'x';

      /*
       * Finally fill in magic stuff like suid and sticky text.
       */
      if (mode & S_ISUID)
            buf[3] = ((mode & S_IXUSR) ? 's' : 'S');
      if (mode & S_ISGID)
            buf[6] = ((mode & S_IXGRP) ? 's' : 'S');
      if (mode & S_ISVTX)
            buf[9] = ((mode & S_IXOTH) ? 't' : 'T');

      return buf;
}


/*
 * Get the time string to be used for a file.
 * This is down to the minute for new files, but only the date for old files.
 * The string is returned from a static buffer, and so is overwritten for
 * each call.
 */
const char *
timeString(time_t timeVal)
{
      time_t            now;
      char *            str;
      static      char  buf[26];

      time(&now);

      str = ctime(&timeVal);

      strcpy(buf, &str[4]);
      buf[12] = '\0';

      if ((timeVal > now) || (timeVal < now - 365*24*60*60L))
      {
            strcpy(&buf[7], &str[20]);
            buf[11] = '\0';
      }

      return buf;
}


/*
 * Return TRUE if a fileName is a directory.
 * Nonexistant files return FALSE.
 */
BOOL
isDirectory(const char * name)
{
      struct      stat  statBuf;

      if (stat(name, &statBuf) < 0)
            return FALSE;

      return S_ISDIR(statBuf.st_mode);
}


/*
 * Return TRUE if a filename is a block or character device.
 * Nonexistant files return FALSE.
 */
BOOL
isDevice(const char * name)
{
      struct      stat  statBuf;

      if (stat(name, &statBuf) < 0)
            return FALSE;

      return S_ISBLK(statBuf.st_mode) || S_ISCHR(statBuf.st_mode);
}


/*
 * Copy one file to another, while possibly preserving its modes, times,
 * and modes.  Returns TRUE if successful, or FALSE on a failure with an
 * error message output.  (Failure is not indicted if the attributes cannot
 * be set.)
 */
BOOL
copyFile(
      const char *      srcName,
      const char *      destName,
      BOOL        setModes
)
{
      int         rfd;
      int         wfd;
      int         rcc;
      char        buf[BUF_SIZE];
      struct      stat  statBuf1;
      struct      stat  statBuf2;
      struct      utimbuf     times;
      
      if (stat(srcName, &statBuf1) < 0)
      {
            perror(srcName);

            return FALSE;
      }

      if (stat(destName, &statBuf2) < 0)
      {
            statBuf2.st_ino = -1;
            statBuf2.st_dev = -1;
      }

      if ((statBuf1.st_dev == statBuf2.st_dev) &&
            (statBuf1.st_ino == statBuf2.st_ino))
      {
            fprintf(stderr, "Copying file \"%s\" to itself\n", srcName);

            return FALSE;
      }

      rfd = open(srcName, O_RDONLY);

      if (rfd < 0)
      {
            perror(srcName);

            return FALSE;
      }

      wfd = creat(destName, statBuf1.st_mode);

      if (wfd < 0)
      {
            perror(destName);
            close(rfd);

            return FALSE;
      }

      while ((rcc = read(rfd, buf, sizeof(buf))) > 0)
      {
            if (intFlag)
            {
                  close(rfd);
                  close(wfd);

                  return FALSE;
            }

            if (fullWrite(wfd, buf, rcc) < 0)
                  goto error_exit;
      }

      if (rcc < 0)
      {
            perror(srcName);
            goto error_exit;
      }

      (void) close(rfd);

      if (close(wfd) < 0)
      {
            perror(destName);

            return FALSE;
      }

      if (setModes)
      {
            (void) chmod(destName, statBuf1.st_mode);

            (void) chown(destName, statBuf1.st_uid, statBuf1.st_gid);

            times.actime = statBuf1.st_atime;
            times.modtime = statBuf1.st_mtime;

            (void) utime(destName, &times);
      }

      return TRUE;


error_exit:
      close(rfd);
      close(wfd);

      return FALSE;
}


/*
 * Build a path name from the specified directory name and file name.
 * If the directory name is NULL, then the original fileName is returned.
 * The built path is in a static area, and is overwritten for each call.
 */
const char *
buildName(const char * dirName, const char * fileName)
{
      const char *      cp;
      static      char  buf[PATH_LEN];

      if ((dirName == NULL) || (*dirName == '\0'))
            return fileName;

      cp = strrchr(fileName, '/');

      if (cp)
            fileName = cp + 1;

      strcpy(buf, dirName);
      strcat(buf, "/");
      strcat(buf, fileName);

      return buf;
}


/*
 * Expand the wildcards in a fileName wildcard pattern, if any.
 * Returns an argument list with matching fileNames in sorted order.
 * The expanded names are stored in memory chunks which can later all
 * be freed at once.  The returned list is only valid until the next
 * call or until the next command.  Returns zero if the name is not a
 * wildcard, or returns the count of matched files if the name is a
 * wildcard and there was at least one match, or returns -1 if either
 * no fileNames matched or there was an allocation error.
 */
int
expandWildCards(const char * fileNamePattern, const char *** retFileTable)
{
      const char *      last;
      const char *      cp1;
      const char *      cp2;
      const char *      cp3;
      char *            str;
      DIR *       dirp;
      struct dirent *   dp;
      int         dirLen;
      int         newFileTableSize;
      char **           newFileTable;
      char        dirName[PATH_LEN];

      static int  fileCount;
      static int  fileTableSize;
      static char **    fileTable;

      /*
       * Clear the return values until we know their final values.
       */
      fileCount = 0;
      *retFileTable = NULL;

      /*
       * Scan the file name pattern for any wildcard characters.
       */
      cp1 = strchr(fileNamePattern, '*');
      cp2 = strchr(fileNamePattern, '?');
      cp3 = strchr(fileNamePattern, '[');

      /*
       * If there are no wildcard characters then return zero to
       * indicate that there was actually no wildcard pattern.
       */
      if ((cp1 == NULL) && (cp2 == NULL) && (cp3 == NULL))
            return 0;

      /*
       * There are wildcards in the specified filename.
       * Get the last component of the file name.
       */
      last = strrchr(fileNamePattern, '/');

      if (last)
            last++;
      else
            last = fileNamePattern;

      /*
       * If any wildcards were found before the last filename component
       * then return an error.
       */
      if ((cp1 && (cp1 < last)) || (cp2 && (cp2 < last)) ||
            (cp3 && (cp3 < last)))
      {
            fprintf(stderr,
            "Wildcards only implemented for last file name component\n");

            return -1;
      }

      /*
       * Assume at first that we are scanning the current directory.
       */
      dirName[0] = '.';
      dirName[1] = '\0';

      /*
       * If there was a directory given as part of the file name then
       * copy it and null terminate it.
       */
      if (last != fileNamePattern)
      {
            memcpy(dirName, fileNamePattern, last - fileNamePattern);
            dirName[last - fileNamePattern - 1] = '\0';

            if (dirName[0] == '\0')
            {
                  dirName[0] = '/';
                  dirName[1] = '\0';
            }
      }

      /*
       * Open the directory containing the files to be checked.
       */
      dirp = opendir(dirName);

      if (dirp == NULL)
      {
            perror(dirName);

            return -1;
      }

      /*
       * Prepare the directory name for use in making full path names.
       */
      dirLen = strlen(dirName);

      if (last == fileNamePattern)
      {
            dirLen = 0;
            dirName[0] = '\0';
      }
      else if (dirName[dirLen - 1] != '/')
      {
            dirName[dirLen++] = '/';
            dirName[dirLen] = '\0';
      }

      /*
       * Find all of the files in the directory and check them against
       * the wildcard pattern.
       */
      while ((dp = readdir(dirp)) != NULL)
      {
            /*
             * Skip the current and parent directories.
             */
            if ((strcmp(dp->d_name, ".") == 0) ||
                  (strcmp(dp->d_name, "..") == 0))
            {
                  continue;
            }

            /*
             * If the file name doesn't match the pattern then skip it.
             */
            if (!match(dp->d_name, last))
                  continue;

            /*
             * This file name is selected.
             * See if we need to reallocate the file name table.
             */
            if (fileCount >= fileTableSize)
            {
                  /*
                   * Increment the file table size and reallocate it.
                   */
                  newFileTableSize = fileTableSize + EXPAND_ALLOC;

                  newFileTable = (char **) realloc((char *) fileTable,
                        (newFileTableSize * sizeof(char *)));

                  if (newFileTable == NULL)
                  {
                        fprintf(stderr, "Cannot allocate file list\n");
                        closedir(dirp);

                        return -1;
                  }

                  fileTable = newFileTable;
                  fileTableSize = newFileTableSize;
            }

            /*
             * Allocate space for storing the file name in a chunk.
             */
            str = getChunk(dirLen + strlen(dp->d_name) + 1);

            if (str == NULL)
            {
                  fprintf(stderr, "No memory for file name\n");
                  closedir(dirp);

                  return -1;
            }

            /*
             * Save the file name in the chunk.
             */
            if (dirLen)
                  memcpy(str, dirName, dirLen);

            strcpy(str + dirLen, dp->d_name);

            /*
             * Save the allocated file name into the file table.
             */
            fileTable[fileCount++] = str;
      }

      /*
       * Close the directory and check for any matches.
       */
      closedir(dirp);

      if (fileCount == 0)
      {
            fprintf(stderr, "No matches\n");

            return -1;
      }

      /*
       * Sort the list of file names.
       */
      qsort((void *) fileTable, fileCount, sizeof(char *), nameSort);

      /*
       * Return the file list and count.
       */
      *retFileTable = (const char **) fileTable;

      return fileCount;
}


/*
 * Sort routine for list of fileNames.
 */
int
nameSort(const void * p1, const void * p2)
{
      const char **     s1;
      const char **     s2;

      s1 = (const char **) p1;
      s2 = (const char **) p2;

      return strcmp(*s1, *s2);
}


/*
 * Routine to see if a text string is matched by a wildcard pattern.
 * Returns TRUE if the text is matched, or FALSE if it is not matched
 * or if the pattern is invalid.
 *  *       matches zero or more characters
 *  ?       matches a single character
 *  [abc]   matches 'a', 'b' or 'c'
 *  \c            quotes character c
 *  Adapted from code written by Ingo Wilken.
 */
BOOL
match(const char * text, const char * pattern)
{
      const char *      retryPat;
      const char *      retryText;
      int         ch;
      BOOL        found;

      retryPat = NULL;
      retryText = NULL;

      while (*text || *pattern)
      {
            ch = *pattern++;

            switch (ch)
            {
                  case '*':  
                        retryPat = pattern;
                        retryText = text;
                        break;

                  case '[':  
                        found = FALSE;

                        while ((ch = *pattern++) != ']')
                        {
                              if (ch == '\\')
                                    ch = *pattern++;

                              if (ch == '\0')
                                    return FALSE;

                              if (*text == ch)
                                    found = TRUE;
                        }

                        if (!found)
                        {
                              pattern = retryPat;
                              text = ++retryText;
                        }

                        /* fall into next case */

                  case '?':  
                        if (*text++ == '\0')
                              return FALSE;

                        break;

                  case '\\':  
                        ch = *pattern++;

                        if (ch == '\0')
                              return FALSE;

                        /* fall into next case */

                  default:        
                        if (*text == ch)
                        {
                              if (*text)
                                    text++;
                              break;
                        }

                        if (*text)
                        {
                              pattern = retryPat;
                              text = ++retryText;
                              break;
                        }

                        return FALSE;
            }

            if (pattern == NULL)
                  return FALSE;
      }

      return TRUE;
}


/*
 * Take a command string and break it up into an argc, argv list while
 * handling quoting and wildcards.  The returned argument list and
 * strings are in static memory, and so are overwritten on each call.
 * The argument list is ended with a NULL pointer for convenience.
 * Returns TRUE if successful, or FALSE on an error with a message
 * already output.
 */
BOOL
makeArgs(const char * cmd, int * retArgc, const char *** retArgv)
{
      const char *            argument;
      char *                  cp;
      char *                  cpOut;
      char *                  newStrings;
      const char **           fileTable;
      const char **           newArgTable;
      int               newArgTableSize;
      int               fileCount;
      int               len;
      int               ch;
      int               quote;
      BOOL              quotedWildCards;
      BOOL              unquotedWildCards;

      static int        stringsLength;
      static char *           strings;
      static int        argCount;
      static int        argTableSize;
      static const char **    argTable;

      /*
       * Clear the returned values until we know them.
       */
      argCount = 0;
      *retArgc = 0;
      *retArgv = NULL;

      /*
       * Copy the command string into a buffer that we can modify,
       * reallocating it if necessary.
       */
      len = strlen(cmd) + 1;

      if (len > stringsLength)
      {
            newStrings = realloc(strings, len);

            if (newStrings == NULL)
            {
                  fprintf(stderr, "Cannot allocate string\n");

                  return FALSE;
            }

            strings = newStrings;
            stringsLength = len;
      }

      memcpy(strings, cmd, len);
      cp = strings;

      /*
       * Keep parsing the command string as long as there are any
       * arguments left.
       */
      while (*cp)
      {
            /*
             * Save the beginning of this argument.
             */
            argument = cp;
            cpOut = cp;

            /*
             * Reset quoting and wildcarding for this argument.
             */
            quote = '\0';
            quotedWildCards = FALSE;
            unquotedWildCards = FALSE;

            /*
             * Loop over the string collecting the next argument while
             * looking for quoted strings or quoted characters, and
             * remembering whether there are any wildcard characters
             * in the argument.
             */
            while (*cp)
            {
                  ch = *cp++;

                  /*
                   * If we are not in a quote and we see a blank then
                   * this argument is done.
                   */
                  if (isBlank(ch) && (quote == '\0'))
                        break;

                  /*
                   * If we see a backslash then accept the next
                   * character no matter what it is.
                   */
                  if (ch == '\\')
                  {
                        ch = *cp++;

                        /*
                         * Make sure there is a next character.
                         */
                        if (ch == '\0')
                        {
                              fprintf(stderr,
                                    "Bad quoted character\n");

                              return FALSE;
                        }

                        /*
                         * Remember whether the quoted character
                         * is a wildcard.
                         */
                        if (isWildCard(ch))
                              quotedWildCards = TRUE;

                        *cpOut++ = ch;

                        continue;
                  }

                  /*
                   * If we see one of the wildcard characters then
                   * remember whether it was seen inside or outside
                   * of quotes.
                   */
                  if (isWildCard(ch))
                  {
                        if (quote)
                              quotedWildCards = TRUE;
                        else
                              unquotedWildCards = TRUE;
                  }

                  /*
                   * If we were in a quote and we saw the same quote
                   * character again then the quote is done.
                   */
                  if (ch == quote)
                  {
                        quote = '\0';

                        continue;
                  }

                  /*
                   * If we weren't in a quote and we see either type
                   * of quote character, then remember that we are
                   * now inside of a quote.
                   */
                  if ((quote == '\0') && ((ch == '\'') || (ch == '"')))
                  {
                        quote = ch;

                        continue;
                  }

                  /*
                   * Store the character.
                   */
                  *cpOut++ = ch;
            }

            /*
             * Make sure that quoting is terminated properly.
             */
            if (quote)
            {
                  fprintf(stderr, "Unmatched quote character\n");

                  return FALSE;
            }

            /*
             * Null terminate the argument if it had shrunk, and then
             * skip over all blanks to the next argument, nulling them
             * out too.
             */
            if (cp != cpOut)
                  *cpOut = '\0';

            while (isBlank(*cp))
                  *cp++ = '\0';

            /*
             * If both quoted and unquoted wildcards were used then
             * complain since we don't handle them properly.
             */
            if (quotedWildCards && unquotedWildCards)
            {
                  fprintf(stderr,
                        "Cannot use quoted and unquoted wildcards\n");

                  return FALSE;
            }

            /*
             * Expand the argument into the matching filenames or accept
             * it as is depending on whether there were any unquoted
             * wildcard characters in it.
             */
            if (unquotedWildCards)
            {
                  /*
                   * Expand the argument into the matching filenames.
                   */
                  fileCount = expandWildCards(argument, &fileTable);

                  /*
                   * Return an error if the wildcards failed to match.
                   */
                  if (fileCount < 0)
                        return FALSE;

                  if (fileCount == 0)
                  {
                        fprintf(stderr, "Wildcard expansion error\n");

                        return FALSE;
                  }
            }
            else
            {
                  /*
                   * Set up to only store the argument itself.
                   */
                  fileTable = &argument;
                  fileCount = 1;
            }

            /*
             * Now reallocate the argument table to hold the file name.
             */
            if (argCount + fileCount >= argTableSize)
            {
                  newArgTableSize = argCount + fileCount + 1;

                  newArgTable = (const char **) realloc(argTable,
                        (sizeof(const char *) * newArgTableSize));

                  if (newArgTable == NULL)
                  {
                        fprintf(stderr, "No memory for arg list\n");

                        return FALSE;
                  }

                  argTable = newArgTable;
                  argTableSize = newArgTableSize;
            }

            /*
             * Copy the new arguments to the end of the old ones.
             */
            memcpy((void *) &argTable[argCount], (const void *) fileTable,
                  (sizeof(const char **) * fileCount));

            /*
             * Add to the argument count.
             */
            argCount += fileCount;
      }

      /*
       * Null terminate the argument list and return it.
       */
      argTable[argCount] = NULL;

      *retArgc = argCount;
      *retArgv = argTable;

      return TRUE;
}


/*
 * Make a NULL-terminated string out of an argc, argv pair.
 * Returns TRUE if successful, or FALSE if the string is too long,
 * with an error message given.  This does not handle spaces within
 * arguments correctly.
 */
BOOL
makeString(
      int         argc,
      const char **     argv,
      char *            buf,
      int         bufLen
)
{
      int   len;

      while (argc-- > 0)
      {
            len = strlen(*argv);

            if (len >= bufLen)
            {
                  fprintf(stderr, "Argument string too long\n");

                  return FALSE;
            }

            strcpy(buf, *argv++);

            buf += len;
            bufLen -= len;

            if (argc)
                  *buf++ = ' ';

            bufLen--; 
      }

      *buf = '\0';

      return TRUE;
}


/*
 * Allocate a chunk of memory (like malloc).
 * The difference, though, is that the memory allocated is put on a
 * list of chunks which can be freed all at one time.  You CAN NOT free
 * an individual chunk.
 */
char *
getChunk(int size)
{
      CHUNK *     chunk;

      if (size < CHUNK_INIT_SIZE)
            size = CHUNK_INIT_SIZE;

      chunk = (CHUNK *) malloc(size + sizeof(CHUNK) - CHUNK_INIT_SIZE);

      if (chunk == NULL)
            return NULL;

      chunk->next = chunkList;
      chunkList = chunk;

      return chunk->data;
}


/*
 * Duplicate a string value using the chunk allocator.
 * The returned string cannot be individually freed, but can only be freed
 * with other strings when freeChunks is called.  Returns NULL on failure.
 */
char *
chunkstrdup(const char * str)
{
      int   len;
      char *      newStr;

      len = strlen(str) + 1;
      newStr = getChunk(len);

      if (newStr)
            memcpy(newStr, str, len);

      return newStr;
}


/*
 * Free all chunks of memory that had been allocated since the last
 * call to this routine.
 */
void
freeChunks(void)
{
      CHUNK *     chunk;

      while (chunkList)
      {
            chunk = chunkList;
            chunkList = chunk->next;
            free((char *) chunk);
      }
}


/*
 * Write all of the supplied buffer out to a file.
 * This does multiple writes as necessary.
 * Returns the amount written, or -1 on an error.
 */
int
fullWrite(int fd, const char * buf, int len)
{
      int   cc;
      int   total;

      total = 0;

      while (len > 0)
      {
            cc = write(fd, buf, len);

            if (cc < 0)
                  return -1;

            buf += cc;
            total+= cc;
            len -= cc;
      }

      return total;
}


/*
 * Read all of the supplied buffer from a file.
 * This does multiple reads as necessary.
 * Returns the amount read, or -1 on an error.
 * A short read is returned on an end of file.
 */
int
fullRead(int fd, char * buf, int len)
{
      int   cc;
      int   total;

      total = 0;

      while (len > 0)
      {
            cc = read(fd, buf, len);

            if (cc < 0)
                  return -1;

            if (cc == 0)
                  break;

            buf += cc;
            total+= cc;
            len -= cc;
      }

      return total;
}

/* END CODE */

Generated by  Doxygen 1.6.0   Back to index