Logo Search packages:      
Sourcecode: bazaar version File versions

file-names.c

/* file-names.c - file-name manipulations
 *
 ****************************************************************
 * Copyright (C) 1998, 2000 Thomas Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */



#include "hackerlab/os/alloca.h"
#include "hackerlab/os/stdlib.h"
#include "hackerlab/os/pwd.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/vu/vu.h"
#include "hackerlab/fs/file-names.h"


/************************************************************************
 *(h0 "File-name Manipulation Functions"
 *    :include ("fs/file-names.h"))
 * 
 * The functions in this chapter are for managing strings which are
 * file names.  Though the actual implementation is unix-specific, the
 * calling conventions for these functions (based on the design of GNU
 * Emacs) should support other operating systems as well.
 *
 * \WARNING: some of the functions in this chapter use `must_malloc' 
 * when it would be better to use some other allocation function.
 * This is likely to be fixed in a future release./
 */
/*(menu)
 */

/************************************************************************
 *(h1 "Home Directories")
 * 
 * 
 * 
 */

/*(c file_name_home_directory)
 * char * file_name_home_directory (void);
 *
 * Return the user's home directory.
 *
 * The path returned should not be freed by the caller and
 * may be returned more than once by `file_name_home_directory'.
 *
 * The home directory is defined to be the value of the
 * environment variable HOME, unless that variable is unbound
 * or is bound to an empty string.  If HOME is undefined, then
 * the home directory is found using `getpwuid' and the 
 * real user id of the process.
 *
 * If the home directory can not be computed, or storage can not
 * be allocated, `panic' is called to force the process to exit.
 *
 * The directory name returned is formatted as if returned
 * by `file_name_as_directory' (it will end with a single `/').
 */
t_uchar *
file_name_home_directory (void)
{
  static t_uchar * path = 0;
  t_uchar * env;
  int uid;
  struct passwd * pwent;

  if (path)
    return path;

  env = getenv ("HOME");
  if (env && env[0])
    path = file_name_as_directory (lim_use_must_malloc, env);
  else
    {
      uid = getuid ();
      pwent = getpwuid (uid);
      if (!pwent)
      panic ("unable to compute a home directory");
      path = file_name_as_directory (lim_use_must_malloc, pwent->pw_dir);
    }
  return path;
}


/*(c file_name_is_absolute)
 * int file_name_is_absolute (t_uchar * f);
 * 
 *    return f && f[0] && (f[0] == '/');
 */
int
file_name_is_absolute (t_uchar * f)
{
  return f && f[0] && (f[0] == '/');
}


/*(c file_name_tilde_expand)
 * t_uchar * file_name_tilde_expand (alloc_limits limits, char * fname);
 *
 * Return a newly allocated copy of `fname' with a leading `~' or
 * `~user' expanded to the appropriate home directory.
 *
 * If allocation fails (but does not call `panic') this function returns 0.
 * 
 * If asked to expand `~', and unable to find a home directory for the
 * current process, this function panics and exits.
 *
 * If asked to expand `~user', and unable to find a home directory for
 * `user', this function returns a newly allocated copy of `fname'.
 *
 */
t_uchar *
file_name_tilde_expand (alloc_limits limits, t_uchar * fname)
{
  if (fname[0] != '~')
    return str_save (limits, fname);
  else if (!fname[1] || (fname[1] == '/'))
    {
      t_uchar * home;
      home = file_name_home_directory ();
      if (!fname[1])
      {
        /* Return a file name without a trailing "/"
         */
        return file_name_from_directory (limits, home);
      }
      else if (!fname[2])
      {
        /* Return a file name with a trailing "/"
         */
        return str_save (limits, home);
      }
      else
      {
        t_uchar * pos;
        pos = fname + 2;
        while (*pos == '/')
          {
            ++pos;
          }
        if (!*pos)
          {
            /* Return a file name with a trailing "/"
             */
            return str_save (limits, home);
          }
        else
          {
            /* Combine the ~-expansion with the remaining file-name.
             */
            return file_name_in_vicinity (limits, home, pos);
          }
      }
    }
  else
    {
      t_uchar * uname_end;
      int uname_len;
      t_uchar * uname;
      struct passwd * pwent;

      uname_end = str_chr_index (fname + 1, '/');
      if (!uname_end)
      uname_end = fname + str_length (fname);

      uname_len = uname_end - (fname + 1);
      uname = (t_uchar *)alloca (uname_len + 1);
      str_cpy_n (uname, fname + 1, uname_len);
      uname[uname_len] = 0;
      
      pwent = getpwnam (uname);
      if (!pwent)
      {
        /* unknown user -- return fname unmodified
         */
        return str_save (limits, fname);
      }
      if (*uname_end && uname_end[1])
      {
        /* ~uname/relative-path
         *
         * Return relative-path relative to the expanded
         * home directory.
         */
        return file_name_in_vicinity (limits, pwent->pw_dir, uname_end + 1);
      }
      else if (*uname_end)
      {
        /* ~uname/
         * 
         * Return the expanded home directory, with
         * a trailing "/".
         */
        return file_name_as_directory (limits, pwent->pw_dir);
      }
      else
      {
        /* ~uname
         * 
         * Return the expanded home directory, without
         * a trailing "/".
         */
        return file_name_from_directory (limits, pwent->pw_dir);
      }
    }
}

/************************************************************************
 *(h1 "File-name Algebra")
 * 
 * 
 * 
 */


/*(c file_name_tail)
 * char * file_name_tail (alloc_limits limits, char * fname);
 * 
 * Return a newly allocated string containing only the last element of
 * the file-name `fname'.  The last element is everything following
 * the last `"/"' or all of `fname' if it contains no `"/"'
 * characters.
 *
 *    "/usr/bin/ls"     =>    "ls"
 *    "/usr/bin/" =>    ""
 *    "ls"        =>    "ls"
 *
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar *
file_name_tail (alloc_limits limits, t_uchar * fname)
{
  t_uchar * s;

  s = str_chr_rindex (fname, '/');
  return str_save (limits, s ? s + 1 : fname);
}


/*(c file_name_head)
 * t_uchar * file_name_head (alloc_limits limits, char * fname);
 * 
 * "/usr/bin/ls"  =>    "/"
 * "usr/bin/ls"         =>    "usr"
 * "ls"                 =>    "ls"
 * 0              =>    0
 */
t_uchar *
file_name_head (alloc_limits limits, char * fname)
{
  t_uchar * slash;

  if (!fname)
    return 0;

  if (fname[0] == '/')
    return str_save (limits, "/");

  slash = str_chr_index (fname, '/');

  if (!slash)
    return str_save (limits, fname);
  else
    return str_save_n (limits, fname, (char *)slash - fname);
}


/*(c file_name_sans_head)
 * t_uchar * file_name_sans_head (alloc_limits limits, char * fname);
 * 
 * "/"                  =>    0
 * "/usr/bin/ls"  =>    "usr/bin/ls"
 * "usr/bin/ls"         =>    "bin/ls"
 * "usr/"         =>    0
 * "ls"                 =>    0
 * 0              =>    0
 */
t_uchar *
file_name_sans_head (alloc_limits limits, char * fname)
{
  t_uchar * slash;

  if (!fname)
    return 0;

  if ((fname[0] == '/') && !fname[1])
    return 0;

  if (fname[0] == '/')
    return str_save (limits, fname + 1);

  slash = str_chr_index (fname, '/');

  if (!slash || !slash[1])
    return 0;
  else
    return str_save (limits, slash + 1);
}


/*(c file_name_as_directory)
 * t_uchar * file_name_as_directory (alloc_limits limits, t_uchar * f);
 * 
 * Return a newly allocated copy of file-name `f', ensuring that the
 * copy ends in a single `"/"'.
 *
 *    "/usr/bin/ls"     =>    "/usr/bin/ls/"
 *    "/usr/bin/" =>    "/usr/bin/"
 *    "/usr/bin///"     =>    "/usr/bin/"
 *    "ls"        =>    "ls/"
 *    "/"         =>    "/"
 *
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar *
file_name_as_directory (alloc_limits limits, t_uchar * f)
{
  int len;
  int already_is;

  len = str_length (f);
  if (!len)
    return str_save (limits, f);
  already_is = (f[len - 1] == '/');
  while (len && (f[len - 1] == '/'))
    --len;

  if (already_is)
    return str_save_n (limits, f, len + 1);
  else
    {
      t_uchar * answer;
      answer = lim_malloc (limits, len + 2);
      if (!answer)
      return 0;
      mem_move (answer, f, len);
      answer[len] = '/';
      answer[len + 1] = 0;
      return answer;
    }
}


/*(c file_name_from_directory)
 * t_uchar * file_name_from_directory (alloc_limits limits, t_uchar * f);
 * 
 * Return a newly allocated copy of file-name `f', removing all 
 * trailing `"/"' characters.  The root directory is treated specially:
 *
 *    "/usr/bin/ls"     =>    "/usr/bin/ls"
 *    "ls"        =>    "ls"
 *    "/usr/bin/" =>    "/usr/bin"
 *    "/usr/bin///"     =>    "/usr/bin"
 *    "/"         =>    "/"
 *
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar * 
file_name_from_directory (alloc_limits limits, t_uchar * f)
{
  t_uchar * answer;
  int len;

  answer = str_save (limits, f);
  if (!answer)
    return 0;
  len = str_length (f);
  if (len == 1)
    return answer;
  while (len-- && (answer[len] == '/'))
    answer[len] = 0;
  return answer;
}


/*(c file_name_directory)
 * t_uchar * file_name_directory (alloc_limits limits, t_uchar * f);
 * 
 * Return a newly allocated copy of the directory portion of file-name
 * `f', ensuring that the copy ends with a single `"/"'.
 *
 * The directory portion is everything except the last component of a
 * file-name.  If `f' already ends with a `"/"', then this function is
 * the same as `file_name_as_directory'.  If `f' has no directory
 * component, this function returns 0:
 *
 *    "/usr/bin/ls"     =>    "/usr/bin/"
 *    "/usr/bin///ls"   =>    "/usr/bin/"
 *    "/usr/bin/" =>    "/usr/bin/"
 *    "/usr/bin"  =>    "/usr/"
 *    "ls"        =>    0
 *    "/"         =>    "/"
 * 
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar *
file_name_directory (alloc_limits limits, t_uchar * f)
{
  int len;
  int already_is;

  len = str_length (f);
  if (!len)
    return 0;
  already_is = (f[len - 1] == '/');
  while (len && (f[len - 1] == '/'))
    --len;

  if (already_is)
    return str_save_n (limits, f, len + 1);
  else
    {
      t_uchar * answer;
      while (len && (f[len - 1] != '/'))
      --len;
      if (!len)
      return 0;
      while (len && (f[len - 1] == '/'))
      --len;
      ++len;
      answer = lim_malloc (limits, len + 1);
      if (!answer)
      return 0;
      mem_move (answer, f, len);
      answer[len] = 0;
      return answer;
    }
}



/*(c file_name_directory_file)
 * t_uchar * file_name_directory_file (alloc_limits limits, t_uchar * f);
 * 
 * Return a newly allocated copy of the directory portion of file-name
 * `f', ensuring that the copy does not end with a single `"/"' (with
 * one exception).
 *
 * The directory portion is everything except the last component of a
 * file-name.  If `f' has no directory component, this function returns 0:
 *
 *    "/usr/bin/ls"     =>    "/usr/bin"
 *    "/usr/bin///ls"   =>    "/usr/bin"
 *    "/usr/bin/" =>    "/usr/bin"
 *    "/usr/bin"  =>    "/usr"
 *    "ls"        =>    0
 *    "/"         =>    "/"
 * 
 * If allocation fails (but does not call `panic') this function returns 0.
 * 
 */
t_uchar *
file_name_directory_file (alloc_limits limits, t_uchar * f)
{
  t_uchar * intermediate;
  t_uchar * answer;

  intermediate = file_name_directory (limits, f);
  if (!intermediate)
    return 0;

  answer = file_name_from_directory (limits, intermediate);
  lim_free (limits, intermediate);
  return answer;
}


/*(c file_name_is_path_prefix)
 * int file_name_is_path_prefix (t_uchar * maybe_prefix, t_uchar * path);
 * 
 * Return non-zero if `maybe_prefix' is equal to `path' or to some
 * path-prefix of `path'.
 * 
 * For example:
 * 
 *    "/usr/bin"  is a prefix of    "/usr/bin/ls"
 *    "/usr/bin"  is a prefix of    "/usr/bin"
 *    "/usr/bin"  is a prefix of    "/usr/bin/"
 *    "/usr/bin/" is a prefix of    "/usr/bin/"
 * 
 * but:
 * 
 *    "/usr/bin"  is _not_ a prefix of    "/usr/bin-utils/ls"
 *    "/usr/bin/" is _not_ a prefix of    "/usr/bin"
 * 
 */
int
file_name_is_path_prefix (t_uchar * maybe_prefix, t_uchar * path)
{
  int len;

  if (str_cmp_prefix (maybe_prefix, path))
    return 0;

  len = str_length (maybe_prefix);

  if (path[len] && (path[len] != '/'))
    return 0;

  return 1;
}


/*(c file_name_in_vicinity)
 * t_uchar * file_name_in_vicinity (alloc_limits limits,
 *                          t_uchar const * dir,
 *                          t_uchar const * file);
 * 
 * Return a newly allocated string which is `dir', followed by `"/"',
 * followed by `file'.  If `dir' is a directory name and `file' is a
 * relative file-name, then the result is a name for `file', relative
 * to `dir'.  If `dir' already ends in `"/"', no additional `"/"' is
 * added.
 *
 * If `dir' is 0 or `""', a newly allocated copy of `file' is returned.
 *
 * If `file' is an absolute file name (begins with `"/"') a newly allocated copy
 * of `file' is returned.
 *
 *    "/usr/bin", "ls"  =>    "/usr/bin/ls"
 *    "/usr/bin/", "ls" =>    "/usr/bin/ls"
 *    "/usr/bin///", "ls"     =>    "/usr/bin/ls"
 *    "/etc", "/usr/bin/ls"   =>    "/usr/bin/ls"
 *    0, "ls"                 =>    "ls"
 *
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar *
file_name_in_vicinity (alloc_limits limits,
                   t_uchar const * dir,
                   t_uchar const * name)
{
  int dl;
  int nl;
  t_uchar * answer;

  if (name && (*name == '/'))
    return str_save (limits, name);

  dl = str_length (dir);
  if (dl && (dir[dl - 1] == '/'))
    {
      while (dl && (dir[dl - 1] == '/'))
      --dl;
      ++dl;
    }
  nl = str_length (name);
  answer = (t_uchar *)lim_malloc (limits, dl + nl + 2);
  if (!answer)
    return 0;
  if (dir && dir[0])
    {
      mem_move (answer, dir, dl);
      if (answer[dl - 1] != '/')
      answer[dl++] = '/';
      str_cpy (answer + dl, name);
    }
  else
    {
      mem_move (answer, name, nl);
      answer[nl] = 0;
    }
  return answer;
}


/************************************************************************
 *(h1 "Paths")
 * 
 * 
 * 
 */

/*(c path_parse)
 * t_uchar ** path_parse (alloc_limits limits, char * path);
 * 
 * Convert a path of file-names separated by ':' to form a 0-terminated
 * array of file-names.
 *
 * An empty file-name is converted to `"."'.
 *
 * The array is allocated by the variable sized array functions and
 * should be freed using `ar_free' (see xref:"Variable Size Arrays").
 * Note that `ar_size' returns the size of the array, including the
 * final 0.
 *
 * The elements of the array are allocated by `must_malloc' and should be
 * freed using `must_free'.  *THIS IS A BUG.*
 *
 * The function `free_path' can be used to free a path allocated by
 * `path_parse'.
 *
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar **
path_parse (alloc_limits limits, t_uchar * path)
{
  t_converter answer;
  t_uchar ** loc;

  answer.void_ptr = 0;
  while (*path)
    {
      if (*path == ':')
      {
        loc = (t_uchar **)ar_push (&answer.void_ptr, limits, sizeof (t_uchar *));
        if (!loc)
          {
            int x;
          espace_error:

            x = ar_size (answer.void_ptr, limits, sizeof (*answer.uchar_ptr_ptr));
            while (x--)
            {
              lim_free (limits, answer.uchar_ptr_ptr[x]);
            }
            ar_free (&answer.void_ptr, limits);
            return 0;
          }
        *loc = (t_uchar *)str_save (limits, ".");
        if (!*loc)
          goto espace_error;
        ++path;
      }
      else
      {
        t_uchar * end;

        end = str_chr_index (path, ':');
        if (!end)
          end = path + str_length (path);
        loc = (t_uchar **)ar_push (&answer.void_ptr, lim_use_must_malloc, sizeof (t_uchar *));
        if (!loc)
          goto espace_error;
        *loc = (t_uchar *)str_save_n (lim_use_must_malloc, path, end - path);
        if (!*loc)
          goto espace_error;
        path = end;
        if ((*path == ':') && (*(path + 1)))
          ++path;
      }
    }
  loc = (t_uchar **)ar_push (&answer.void_ptr, lim_use_must_malloc, sizeof (t_uchar *));
  if (!loc)
    goto espace_error;
  *loc = 0;
  return answer.uchar_ptr_ptr;
}


/*(c free_path)
 * void free_path (alloc_limits limits, t_uchar ** path)
 * 
 * Free a path allocated by `path_parse'.
 */
void
free_path (alloc_limits limits, t_uchar ** path)
{
  if (path)
    {
      t_converter aPath;
      int x;

      aPath.uchar_ptr_ptr = path;

      for (x = 0; path[x]; ++x)
      lim_free (limits, path[x]);

      ar_free (&aPath.void_ptr, limits);
    }
}

/*(c path_find_executable)
 * t_uchar * path_find_executable (alloc_limits limits, 
 *                         t_uchar ** path,
 *                         t_uchar * tail);
 * 
 * Search on `path' for a file named `tail' that is executable by 
 * this process.  Returns a newly allocated copy of the name of such a file 
 * or 0.
 *
 * The file-name returned is formed by `file_name_in_vicinity (limits, path[N], tail)' 
 * and should be freed by `must_free'.
 *
 * The effective user and group ids are used to check whether or not the
 * file can be executed (which should be the same rule used by the kernel).
 * 
 * If allocation fails (but does not call `panic') this function returns 0.
 */
t_uchar *
path_find_executable (alloc_limits limits,
                  t_uchar ** path,
                  t_uchar * tail)
{
  int x;
  t_uchar * answer;
  int euid;
  int egid;

  euid = geteuid ();
  egid = getegid ();
  x = 0;
  while (path[x])
    {
      struct stat stat;
      int stat_ok;
      int errn;
      
      answer = file_name_in_vicinity (limits, path[x], tail);
      if (!answer)
      return 0;
      stat_ok = vu_stat (&errn, answer, &stat);
      if (stat_ok == 0)
      {
        if (euid == stat.st_uid)
          {
            if (stat.st_mode & S_IXUSR)
            return answer;
          }
        if (egid == stat.st_gid)
          {
            if (stat.st_mode & S_IXGRP)
            return answer;
          }
        if (stat.st_mode & S_IXOTH)
          return answer;
      }
      lim_free (limits, answer);
      ++x;
    }

  answer = 0;
  return answer;
}


Generated by  Doxygen 1.6.0   Back to index