/* 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/errno.h"
#include "hackerlab/os/stdlib.h"
#include "hackerlab/os/pwd.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/bugs/exception.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 const * const 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 const * f)
{
t_uchar * intermediate;
t_uchar * answer;
intermediate = file_name_directory (limits, (t_uchar *)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 talloc_file_name_in_vicinity)
* t_uchar * file_name_in_vicinity (void * context,
* t_uchar const * dir,
* t_uchar const * file);
*
* Return a newly allocated string (in context) 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 attempts to
* throw an exception with code ENOMEM.
*/
t_uchar *
talloc_file_name_in_vicinity (void * context,
t_uchar const * dir,
t_uchar const * name)
{
int dl;
int nl;
t_uchar * answer;
if (name && (*name == '/'))
{
answer = talloc_strdup (context, name);
goto done;
}
dl = str_length (dir);
if (dl && (dir[dl - 1] == '/'))
{
while (dl && (dir[dl - 1] == '/'))
--dl;
++dl;
}
nl = str_length (name);
answer = talloc_size(context, dl + nl + 2);
if (!answer)
goto done;
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;
}
done:
if (!answer)
Throw (exception (ENOMEM, "Could not allocate result"));
talloc_set_name_const(answer, answer);
return answer;
}
/*(c file_name_in_vicinity)
* t_uchar * file_name_in_vicinity (alloc_limits limits,
* t_uchar const * dir,
* t_uchar const * file);
*
* Return a str allocated from limits, from the result of
* talloc_file_name_in_vicinity (talloc_context, dir, file);
*
* If allocation fails (but does not call `panic') this function returns 0.
* If an ENONMEM exception is raised, it is caught and NULL returned.
* All other exceptions are rethrown.
*
*/
t_uchar *
file_name_in_vicinity (alloc_limits limits,
t_uchar const * dir,
t_uchar const * name)
{
t_uchar * result = NULL;
struct exception * e;
Try
{
t_uchar * tmp = talloc_file_name_in_vicinity (talloc_context,
dir,
name);
result = str_save (0, tmp);
}
Catch (e)
{
if (e->code != ENOMEM)
Throw (e);
talloc_free (e);
return NULL;
}
return result;
}
/************************************************************************
*(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 == ':')
{
t_uchar *temp = str_save (limits, ".");
if (!temp)
goto espace_error;
loc = ar_push_uchar_star(&answer.uchar_ptr_ptr, temp);
if (!loc)
{
int x;
espace_error:
x = ar_size (answer.void_ptr);
while (x--)
{
lim_free (limits, answer.uchar_ptr_ptr[x]);
}
ar_free_uchar_star (&answer.uchar_ptr_ptr);
return 0;
}
++path;
}
else
{
t_uchar * end;
t_uchar * temp;
end = str_chr_index (path, ':');
if (!end)
end = path + str_length (path);
temp = str_save_n (lim_use_must_malloc, path, end - path);
if (!temp)
goto espace_error;
loc = ar_push_uchar_star (&answer.uchar_ptr_ptr, temp);
if (!loc)
goto espace_error;
path = end;
if ((*path == ':') && (*(path + 1)))
++path;
}
}
loc = ar_push_uchar_star (&answer.uchar_ptr_ptr, NULL);
if (!loc)
goto espace_error;
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)
{
int x;
t_uchar **aPath = path;
for (x = 0; path[x]; ++x)
lim_free (limits, path[x]);
ar_free_uchar_star (&aPath);
}
}
/*(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;
}
syntax highlighted by Code2HTML, v. 0.9.1