summaryrefslogtreecommitdiffstats
path: root/lib/qpath.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/qpath.c')
-rw-r--r--lib/qpath.c1528
1 files changed, 1528 insertions, 0 deletions
diff --git a/lib/qpath.c b/lib/qpath.c
new file mode 100644
index 00000000..955f307f
--- /dev/null
+++ b/lib/qpath.c
@@ -0,0 +1,1528 @@
+/* Some primitive path handling
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "misc.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <pwd.h>
+
+#include "qpath.h"
+#include "qstring.h"
+#include "memory.h"
+#include "pthread_safe.h"
+
+/*==============================================================================
+ * Some primitive path handling, based on qstrings.
+ *
+ *
+ *==============================================================================
+ * Path Reduction
+ *
+ * As per POSIX, multiple '/' count as single '/', except for the very special
+ * case of exactly two '/' at the start of a path. (That case is referred to
+ * here as a "double root".)
+ *
+ * So this code reduces runs of '/' to single '/' -- except for the special
+ * case.
+ *
+ * This code also replaces "/./" by "/", and removes xxxx/.. ! Does not strip
+ * trailing '/'.
+ */
+static void qpath_reduce(qpath qp) ;
+
+/*------------------------------------------------------------------------------
+ * Initialise a brand new qpath -- allocate if required.
+ *
+ * The result is a path with a not-empty body. All qpath values may be assumed
+ * to have a body at all times.
+ *
+ * Returns: address of qpath
+ *
+ * NB: assumes initialising a new structure. If not, then caller should
+ * use qpath_reset() or qpath_clear().
+ */
+extern qpath
+qpath_init_new(qpath qp)
+{
+ if (qp == NULL)
+ qp = XCALLOC(MTYPE_QPATH, sizeof(qpath_t)) ;
+ else
+ memset(qp, 0, sizeof(qpath_t)) ;
+
+ qs_init_new(qp->path, 50) ; /* Always have a body */
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create a new qpath if don't already have one.
+ */
+inline static qpath
+qpath_make_if_null(qpath qp)
+{
+ if (qp == NULL)
+ qp = qpath_init_new(NULL) ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reset contents of given qpath, freeing the structure if required.
+ *
+ * Discards all the contents of the qpath.
+ */
+extern qpath
+qpath_reset(qpath qp, free_keep_b free_structure)
+{
+ if (qp == NULL)
+ return NULL ;
+
+ qs_reset(qp->path, keep_it) ;
+
+ if (free_structure)
+ XFREE(MTYPE_QPATH, qp) ; /* sets qp = NULL */
+ else
+ ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear down given qpath -- retaining any body, but setting it empty.
+ */
+extern qpath
+qpath_clear(qpath qp)
+{
+ if (qp == NULL)
+ return NULL ;
+
+ qs_clear(qp->path) ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given string -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set(qpath dst, const char* src)
+{
+ return qpath_set_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given qstring -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set_qs(qpath dst, const qstring src)
+{
+ return qpath_set_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given string -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ qs_set_n(dst->path, src, n) ;
+
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Copy qpath to the given qpath -- creating qpath if required.
+ * The result is an empty qpath if the given one is NULL.
+ */
+extern qpath
+qpath_copy(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_set_n(dst, qs_char_nn(src->path), qs_len_nn(src->path)) ;
+ else
+ return qpath_set_n(dst, NULL, 0) ;
+} ;
+
+/*==============================================================================
+ * Interfaces to system.
+ */
+
+/*------------------------------------------------------------------------------
+ * Get the current working directory -- creates a qpath if required.
+ *
+ * Returns: the (new) qpath if OK
+ * NULL if not OK -- any existing qpath is cleared.
+ *
+ * If fails will be because some directory on the way back to the root is
+ * not readable or searchable (!).
+ */
+extern qpath
+qpath_getcwd(qpath dst)
+{
+ qpath od ;
+
+ od = dst ;
+ dst = qpath_make_if_null(dst) ;
+
+ qs_new_size(dst->path, 50) ;
+
+ while (1)
+ {
+ void* r ;
+ usize s ;
+
+ s = qs_size_nn(dst->path) ;
+ r = getcwd(qs_char_nn(dst->path), s) ;
+
+ if (r != NULL)
+ {
+ qs_set_strlen_nn(dst->path) ;
+ return dst ; /* exit here if OK. */
+ } ;
+
+ if (errno != ERANGE)
+ break ; /* exit here on failure */
+
+ qs_new_size(dst->path, s * 2) ;
+ } ;
+
+ if (od == NULL)
+ qpath_reset(dst, free_it) ;
+ else
+ qpath_clear(dst) ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the current working directory
+ *
+ * Returns: 0 <=> OK
+ * errno otherwise
+ */
+extern int
+qpath_setcwd(qpath qp)
+{
+ return (chdir(qpath_string(qp)) == 0) ? 0 : errno ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Do "stat" for given path.
+ *
+ * Returns: 0 <=> OK
+ * errno otherwise
+ */
+extern int
+qpath_stat(qpath qp, struct stat* sbuf)
+{
+ return (stat(qpath_string(qp), sbuf) == 0) ? 0 : errno ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is given path a file we might access -- according to stat ?
+ *
+ * Returns: -1 <=> no -- can access etc, but it's not a file
+ * 0 <=> yes
+ * errno otherwise
+ */
+extern int
+qpath_stat_is_file(qpath qp)
+{
+ struct stat sbuf[1] ;
+ int err ;
+
+ err = qpath_stat(qp, sbuf) ;
+
+ return (err == 0) ? (S_ISREG(sbuf->st_mode) ? 0 : -1)
+ : err ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is given path a directory we might access -- according to stat ?
+ *
+ * Returns: -1 <=> no -- can access etc, but it's not a directory
+ * 0 <=> yes
+ * errno otherwise
+ */
+extern int
+qpath_stat_is_directory(qpath qp)
+{
+ struct stat sbuf[1] ;
+ int err ;
+
+ err = qpath_stat(qp, sbuf) ;
+
+ return (err == 0) ? (S_ISDIR(sbuf->st_mode) ? 0 : -1)
+ : err ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get home directory for the given user.
+ *
+ * The name may be terminated by '\0' or '/'.
+ *
+ * If the qp path is NULL, creates a new qpath if required.
+ *
+ * Returns: qpath => OK
+ * NULL => errno == 0 => user not found ) existing qp set empty.
+ * errno != 0 => some error )
+ */
+extern qpath
+qpath_get_home(qpath qp, const char* name)
+{
+ qpath dst ;
+ const char* p ;
+
+ bool done ;
+ qpath home ;
+ int err ;
+
+ /* Prepare for action.
+ */
+ p = name ;
+
+ while ((*p != '\0') && (*p != '/'))
+ ++p ; /* find terminator */
+
+ dst = qpath_set_n(qp, name, p - name) ;
+ /* set user name */
+
+ done = false ; /* no result, yet */
+ home = NULL ; /* empty result, so far */
+ err = 0 ; /* no error, yet */
+
+ /* If the name is empty, attempt to get the HOME environment variable,
+ * failing that set the user name to the getlogin_r() name.
+ */
+ if ((p - name) == 0)
+ {
+ int l, s ;
+
+ while (1)
+ {
+ s = qs_size_nn(dst->path) ;
+ l = getenv_r("HOME", qs_char_nn(dst->path), s) ;
+
+ if (l < s)
+ break ;
+
+ qs_new_size(dst->path, l) ;
+ } ;
+
+ if (l >= 0)
+ {
+ qs_set_len_nn(dst->path, l) ;
+ home = dst ; /* Successfully fetched HOME */
+ done = true ;
+ }
+ else
+ {
+ while (1)
+ {
+ s = qs_size_nn(dst->path) ;
+ qassert(s > 0) ;
+ err = getlogin_r(qs_char_nn(dst->path), s) ;
+
+ if (err != ERANGE)
+ break ;
+
+ qs_new_size(dst->path, s * 2) ;
+ } ;
+
+ if (err == 0)
+ {
+ qs_set_strlen_nn(dst->path) ;
+
+ if (qs_len_nn(dst->path) == 0)
+ done = true ;
+ }
+ else
+ done = true ;
+ } ;
+ } ;
+
+ /* If name was empty, or not found "HOME", then proceed to getpwd_r
+ */
+ if (!done)
+ {
+ struct passwd pwd_s ;
+ struct passwd* pwd ;
+
+ char* scratch ;
+ uint scratch_size ;
+ char buffer[200] ; /* should be plenty */
+
+ scratch = buffer ;
+ scratch_size = sizeof(buffer) ;
+
+ while (1)
+ {
+ err = getpwnam_r(qpath_string(dst),
+ &pwd_s, scratch, scratch_size, &pwd) ;
+ if (err == EINTR)
+ continue ;
+
+ if (err != ERANGE)
+ break ;
+
+ scratch_size *= 2 ;
+ if (scratch == buffer)
+ scratch = XMALLOC(MTYPE_TMP, scratch_size) ;
+ else
+ scratch = XREALLOC(MTYPE_TMP, scratch, scratch_size) ;
+ } ;
+
+ done = true ;
+
+ if (pwd != NULL)
+ {
+ qpath_set(dst, pwd->pw_dir) ;
+ home = dst ;
+ } ;
+
+ if (scratch != buffer)
+ XFREE(MTYPE_TMP, scratch) ;
+ } ;
+
+ /* Complete result
+ */
+ if (home == NULL)
+ {
+ if (qp != NULL)
+ qs_set_len_nn(qp->path, 0) ; /* chop existing path */
+ else
+ qpath_free(dst) ; /* discard temporary */
+
+ errno = err ; /* as promised */
+ } ;
+
+ return home ;
+} ;
+
+/*==============================================================================
+ * Path editing functions
+ *
+ *
+ */
+
+/*------------------------------------------------------------------------------
+ * Shave any file part off the end of the given path.
+ *
+ * This treats anything after the last '/' of the path as being the "file part".
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty -> unchanged
+ *
+ * 2. "aaa" -- file part only -> ""
+ *
+ * 3. "/" -- root only, or -> unchanged
+ * "//" double root only -> unchanged
+ *
+ * 4. "/aaa" -- routed file -> "/"
+ * "//aaa" double routed file -> "//"
+ *
+ * 5. "zzz/" -- no file part -> unchanged
+ *
+ * 6. "zzz/aaa" -- non-empty file part -> "zzz/"
+ *
+ * Ensures that the path is "reduced" before shaving.
+ *
+ * Creates a new, empty path if qp is NULL.
+ */
+extern qpath
+qpath_shave(qpath qp)
+{
+ pp_t p ;
+
+ qp = qpath_make_if_null(qp) ;
+
+ qs_pp_nn(p, qp->path) ;
+
+ /* Track back to last '/' */
+ while ( (p->e > p->p) && (*(p->e - 1) != '/') )
+ --p->e ;
+
+ qs_set_len_nn(qp->path, p->e - p->p) ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * See if path ends '/'.
+ */
+extern bool
+qpath_has_trailing_slash(qpath qp)
+{
+ pp_t p ;
+
+ if (qp == NULL)
+ return false ;
+
+ qs_pp_nn(p, qp->path) ;
+
+ return (p->e > p->p) && (*(p->e - 1) == '/') ;
+} ;
+
+#if 0
+
+/*==============================================================================
+ * Pop the last part of the given path.
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty path
+ *
+ * - path unchanged
+ * - return empty part
+ *
+ * 2. "aaa" -- the path is a single part
+ *
+ * - part removed from path, leaves ""
+ * - return the part
+ *
+ * Note that in (1) and (2) there is no '/', in the following there is at least
+ * one '/'.
+ *
+ * 3. "/" -- root only, or
+ * "//" double root only
+ *
+ * - path unchanged
+ * - return empty part
+ *
+ * 4. "/aaa" -- one remaining part before the root,
+ * "//aaa" or one remaining part after double route
+ *
+ * - part removed from path, leaves "/" or "//"
+ * - return the part
+ *
+ * 5. "zzz/" -- empty last part
+ *
+ * - "/" removed from end of path
+ * - return empty part
+ *
+ * 6. "zzz/aaa" -- non-empty last part
+ *
+ * - part and "/" removed from end of path
+ * - return part
+ *
+ * Note that other forms of multiple '/' have been reduced, already.
+ */
+extern qpath
+qpath_pop(qpath to, qpath from)
+{
+ qpath part ;
+ qstring qs ;
+ char* sp ;
+ char* p ;
+
+ /* Get pointers to start and end of path */
+ if (qp != NULL)
+ {
+ qs = &qp->path ;
+
+ sp = qs_chars(qs) ; /* NULL if qpath is completely empty */
+ p = qs_ep_char(qs) ; /* points at trailing '\0' */
+ }
+ else
+ qs = sp = p = NULL ;
+
+ /* Deal with NULL-ness */
+ if (sp == NULL)
+ {
+ assert(p == sp) ; /* ie qs->len == 0 if qs->body == NULL */
+
+ return qpath_init_new(NULL, NULL) ;
+ } ;
+
+ /* Track back to last '/' */
+ while ( (p > sp) && (*(p - 1) != '/') )
+ --p ;
+
+ /* Construct result which is from p forwards
+ *
+ * p == NULL if the given path is completely empty.
+ */
+ part = qpath_init_new(NULL, p) ;
+
+ /* If what remains is not empty, and not just "/" or "//", remove trailing '/'
+ *
+ * If p == sp, there was no '/', so path ends up empty.
+ * If p == sp + 1, there is one '/', and that is at the front of the path
+ * If p == sp + 2, there is either "//" or "a/"
+ * If p > sp + 2, there is "aa/"
+ */
+ if (p >= (sp + 2)) /* if "//", "a/" or "aa/" etc. */
+ {
+ /* Unless is special case of "//"... */
+ if ( ! ((p == (sp + 2)) && (*sp == '/')) )
+ --p ; /* ... discard trailing '/' */
+ } ;
+
+ qs->len = (p - sp) ; /* set the new length (shorter !) */
+ *p = '\0' ; /* and terminate */
+
+ /* Return the part we hacked off */
+ return part ;
+} ;
+
+
+
+
+/*==============================================================================
+ * Shift off the first part of the given path.
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty path
+ *
+ * - path unchanged
+ * - return empty part
+ *
+ * 2. "aaa" -- the path is a single part
+ *
+ * - part removed from path, leaves ""
+ * - return the part
+ *
+ * Note that in (1) and (2) there is no '/', in the following there is at least
+ * one '/'.
+ *
+ * 3. "/" -- root only, or
+ * "//" double root
+ *
+ * - remove the "/" or "//" -- result is empty
+ * - return "/" or "//" part
+ *
+ * 4. "/azz" -- root followed by stuff, or
+ * "//zzz" double root followed by stuff.
+ *
+ * - remove the "/" or "//"
+ * - return "/" or "//" part
+ *
+ * 5. "aaa/" -- something followed by first '/' which is end of path
+ *
+ * - remove upto and including '/' -- result is empty
+ * - return upto but excluding '/'
+ *
+ * 6. "aaa/azz" -- something followed by first '/' followed by something else
+ *
+ * - remove upto and including '/'
+ * - return upto but excluding '/'
+ *
+ * Note that other forms of multiple '/' have been reduced, already.
+ */
+extern qpath
+qpath_shift(qpath qp)
+{
+ qpath part ;
+ qstring qs ;
+ char* sp ;
+ char* p ;
+ char* q ;
+
+ /* Get pointers to start and end of path */
+ if (qp != NULL)
+ {
+ qs = &qp->path ;
+ sp = qs_chars(qs) ; /* NULL if qpath is completely empty */
+ }
+ else
+ qs = sp = NULL ;
+
+ /* Deal with NULL-ness */
+ if (sp == NULL)
+ return qpath_init_new(NULL, NULL) ;
+
+ p = sp ;
+
+ /* Set p such that sp..p-1 is to be shifted off.
+ * And q such that q..end-1 is to be kept.
+ */
+ if (*sp == '/')
+ {
+ ++p ; /* single root */
+ if (*p == '/')
+ ++p ; /* double root */
+ q = p ;
+ }
+ else
+ {
+ while ((*p != '/') && (*p != '/0'))
+ ++p ; /* step to '/' or end */
+
+ if (*p == '/')
+ q = p + 1 ; /* don't keep '/' */
+ else
+ q = p ; /* keep '\0' ! */
+ } ;
+
+ /* Construct qpath for shifted off stuff */
+ part = qpath_init_new(NULL, NULL) ;
+
+ /* Hack off the shifted off stuff & '/' if required */
+
+
+ /* Return the part we hacked off */
+ return part ;
+} ;
+#endif
+
+/*==============================================================================
+ * Append, Prepend and Complete
+ */
+
+static ulen qpath_trim_home(const char* p, ulen n) ;
+
+/*------------------------------------------------------------------------------
+ * Append one path (src) onto the end of the given path (dst).
+ *
+ * If the dst path is NULL, creates a new, empty qpath to append to.
+ *
+ * The dst path is assumed to be the path to a "directory". An empty dst path
+ * is treated as "the current directory".
+ *
+ * If src path starts '/' or '~', then it is trimmed, removing leading
+ * characters up to and including '/'.
+ *
+ * If dst path is not empty, and does not end '/', then an '/' is appended
+ * before the src is appended.
+ *
+ * Note that this means:
+ *
+ * -- appending an empty path or one which is just "/", will leave the dst
+ * path ending "/" -- unless the dst path is empty.
+ *
+ * -- cannot create a rooted path by appending a path onto an empty path.
+ *
+ * -- appending a "homed" path "~..../" is assumed to be appending to the
+ * required "home".
+ *
+ * The resulting path is reduced (see above).
+ */
+extern qpath
+qpath_append(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_append_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_append_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append src qstring onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_qs(qpath dst, const qstring src)
+{
+ return qpath_append_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append src string onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_str(qpath dst, const char* src)
+{
+ return qpath_append_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append src string of given length onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_str_n(qpath dst, const char* src, ulen n)
+{
+ ulen l ;
+
+ dst = qpath_make_if_null(dst) ;
+
+ /* Trim the path to be appended:
+ *
+ * 1. discard from any leading '~' to the first '/' or to '\0'.
+ *
+ * 2. then discard any leading '/'
+ *
+ * 3. then establish length of result.
+ */
+ l = n ;
+ n = qpath_trim_home(src, n) ;
+ src += (l - n) ; /* step past stuff trimmed */
+
+ /* Worry about whether need to add a '/' to the path before pushing */
+ if (qs_len_nn(dst->path) != 0)
+ {
+ if (*(qs_ep_char_nn(dst->path) - 1) != '/')
+ qs_append_str_n(dst->path, "/", 1) ;
+ } ;
+
+ /* Now append the src */
+ qs_append_str_n(dst->path, src, n) ;
+
+ /* Reduce the new part of the result, and return */
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src path.
+ *
+ * Does not introduce any '/' or any other stuff.
+ *
+ * The resulting path is reduced (see above).
+ */
+extern qpath
+qpath_extend(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_extend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_extend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src qstring.
+ */
+extern qpath
+qpath_extend_qs(qpath dst, const qstring src)
+{
+ return qpath_extend_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src string.
+ */
+extern qpath
+qpath_extend_str(qpath dst, const char* src)
+{
+ return qpath_extend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src string of the given
+ * length.
+ */
+extern qpath
+qpath_extend_str_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ qs_append_str_n(dst->path, src, n) ;
+
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src path onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_prepend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_prepend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src qstring onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_qs(qpath dst, const qstring src)
+{
+ return qpath_prepend_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src string onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_str(qpath dst, const char* src)
+{
+ return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src string of given length onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_str_n(qpath dst, const char* src, ulen n)
+{
+ char* p ;
+ ulen r ;
+ bool need_slash ;
+
+ dst = qpath_make_if_null(dst) ;
+
+ /* Trim the path to be prepended to:
+ *
+ * 1. discard from any leading '~' to the first '/' (or end).
+ *
+ * 2. then discard any leading '/'
+ *
+ * 3. then establish length of any part to be replaced.
+ */
+ r = qs_len_nn(dst->path) ;
+ r -= qpath_trim_home(qs_char_nn(dst->path), r) ;
+
+ /* Worry about whether need to add a '/' to the path before pushing */
+ need_slash = (n > 0) && (*(src + n - 1) != '/') ;
+
+ /* Make room for src and possible slash in qstring */
+ qs_set_cp_nn(dst->path, 0) ;
+ qs_replace(dst->path, r, NULL, n + (need_slash ? 1 : 0)) ;
+
+ /* Now copy in the src */
+ p = qs_char_nn(dst->path) ;
+
+ if (n > 0)
+ memmove(p, src, n) ;
+
+ if (need_slash)
+ *(p + n) = '/' ;
+
+ /* Reduce the new part of the result, and return */
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make a qpath from the given string, completing it, if required, by
+ * prepending the given directory qpath.
+ */
+extern qpath
+qpath_make(const char* src, const qpath dir)
+{
+ if (*src == '/')
+ return qpath_set(NULL, src) ;
+
+ return qpath_append_str(qpath_dup(dir), src) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src path to it. Result is reduced.
+ */
+extern qpath
+qpath_complete(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_prepend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_prepend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src qstring to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_qs(qpath dst, const qstring src)
+{
+ return qpath_complete_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src string to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_str(qpath dst, const char* src)
+{
+ return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src string of given length to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_str_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ if ((qs_len_nn(dst->path) == 0) || (*(qs_char_nn(dst->path)) == '/'))
+ qpath_prepend_str_n(dst, src, n) ;
+ else
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Trim leading '~' up to and including one or more '/'.
+ *
+ * Return remaining length after the trim.
+ */
+static ulen
+qpath_trim_home(const char* p, ulen n)
+{
+ if ((n > 0) && (*p == '~'))
+ {
+ do /* Step past leadin '~' to first '/' */
+ {
+ ++p ;
+ --n ;
+ } while ((n > 0) && (*p != '/')) ;
+ } ;
+
+ while ((n > 0) && (*p == '/'))
+ {
+ ++p ; /* Step past leading '/' */
+ --n ;
+ } ;
+
+ return n ;
+} ;
+
+/*==============================================================================
+ *
+ */
+
+#if 0
+
+/*------------------------------------------------------------------------------
+ * Does the given path start and end '/' ?
+ */
+extern bool
+qpath_sex(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return qp_empty ; /* NULL qpath is empty */
+
+ sp = qs_chars(&qp->path) ;
+
+ if ((sp == NULL) || (*sp == '\0'))
+ return qp_empty ; /* NULL body or just '\0' */
+
+ if (*sp == '~')
+ return qp_homed ;
+
+ if (*sp == '/')
+ {
+ ++sp ;
+ if (*sp == '\0')
+ return qp_root ;
+ if (*sp != '/')
+ return qp_absolute ;
+
+ ++sp ;
+ if (*sp == '\0')
+ return qp_double_root ;
+ else
+ return qp_double_absolute ;
+ } ;
+
+
+
+ qp_empty, /* nothing at all */
+ qp_relative, /* something, not starting '/' */
+ qp_root, /* "/" all on its own */
+ qp_absolute, /* something, starting with single '/' */
+ qp_homed, /* something, starting with '~' */
+ qp_double_root, /* "//" all on its own */
+ qp_double_absolute /* something, starting with "//" */
+
+
+
+ return ((qp->path).len == 1) && (*sp == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is there anything there ?
+ */
+extern bool
+qpath_is_empty(qpath qp)
+{
+ if (qp == NULL)
+ return true ; /* NULL qpath is empty */
+
+ return (qp->path).len == 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is it: not empty, not starting '/' (or '//') and not starting '~'
+ */
+extern bool
+qpath_is_relative(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath is not relative */
+
+ sp = qs_chars(&qp->path) ;
+
+ return (sp != NULL) && (*sp != '/') && (*sp != '.') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start and end '/' ?
+ */
+extern bool
+qpath_is_root(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be root */
+
+ sp = qs_chars(&qp->path) ;
+
+ return ((qp->path).len == 1) && (*sp == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start '/' (and not '//') ?
+ *
+ * Note that just "/" (ie root) will return true => it is also absolute.
+ */
+extern bool
+qpath_is_absolute(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be absolute */
+
+ sp = qs_chars(&qp->path) ;
+
+ return (sp != NULL) && (*sp == '/') && (*(sp + 1) != '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start '~'
+ */
+extern bool
+qpath_is_homed(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be homed */
+
+ sp = qs_chars( &qp->path) ;
+
+ return (sp != NULL) && (*sp == '~') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start and end '/' ?
+ */
+extern bool
+qpath_is_double_root(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be root */
+
+ sp = qs_chars(&qp->path) ;
+
+ return ((qp->path).len == 2) && (*sp == '/') && (*(sp + 1) == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start '/' (and not '//') ?
+ *
+ * Note that just "/" (ie root) will return true => it is also absolute.
+ */
+extern bool
+qpath_is_double_absolute(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be absolute */
+
+ sp = qs_chars(&qp->path) ;
+
+ return (sp != NULL) && (*sp == '/') && (*(sp + 1) == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path *end* '/', or is it ~aaaa with no '/' at all.
+ *
+ * Note that root and double route return directory true.
+ */
+extern bool
+qpath_is_directory(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be directory */
+
+ sp = qs_chars(&qp->path) ;
+
+ if ((sp == NULL) || (*sp = '\0'))
+ return false ; /* Empty qpath cannot be directory */
+
+ ep = qs_ep_char(qs) ;
+
+ if (*(ep - 1) == '/')
+ return true ; /* Ends '/' */
+
+ if (*sp == '~')
+ {
+ while (*(++sp) != '/')
+ {
+ if (*sp == '\0') /* Starts '~' and no '/' found */
+ return true ;
+ } ;
+ } ;
+
+ return false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is the given path an atom
+ *
+ * Note that root and double route are .
+ */
+extern bool
+qpath_is_atom(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be directory */
+
+ sp = qs_chars(&qp->path) ;
+
+ if ((sp == NULL) || (*sp = '\0'))
+ return false ; /* Empty qpath cannot be directory */
+
+ ep = qs_ep_char(qs) ;
+
+ if (*(ep - 1) == '/')
+ return true ; /* Ends '/' */
+
+ if (*sp == '~')
+ {
+ while (*(++sp) != '/')
+ {
+ if (*sp == '\0') /* Starts '~' and no '/' found */
+ return true ;
+ } ;
+ } ;
+
+ return false ;
+} ;
+
+#endif
+
+/*==============================================================================
+ *
+ *
+ *
+ */
+/*------------------------------------------------------------------------------
+ * Reduce multiple '/' to single '/' (except for exactly "//" at start).
+ *
+ * Reduce "zzz/./zzz" to "zzz/zzz" -- s/\/\.(\/|\0)/$1/g
+ * "zzz/./" to "zzz/"
+ * "zzz/." to "zzz/"
+ * "/./" to "/"
+ * "/." to "/"
+ *
+ * where zzz is anything, including '/'
+ *
+ * Reduce "zzz/aaa/../zzz" to "zzz/zzz" -- s/\.*([^/.]+\.*)+\/\.\.\/?//g
+ * "aaa/../zzz" to "zzz"
+ * "/aaa/../zzz" to "/zzz"
+ * "/aaa/../" to "/"
+ * "aaa/../" to ""
+ * "aaa/.." to ""
+ *
+ * where aaa is anything except '/'
+ *
+ * NB:
+ *
+ * NB: does not strip trailing '/' (including '/' of trailing "/." or "/..")
+ *
+ * does not reduce leading "./" or single "." to nothing.
+ */
+static void
+qpath_reduce(qpath qp)
+{
+ char* sp ;
+ char* p ;
+ char* q ;
+ char ch ;
+ bool eat ;
+
+ sp = qs_make_string(qp->path) ;
+ p = sp ;
+
+ /* Deal with special case of "//" at start.
+ *
+ * If find "//x", where x is anything other than '/', step past the first
+ * '/'. Could step past both "//", but that stops it seeing "//./zzz"
+ */
+ if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/'))
+ ++p ;
+
+ /* Scan to see if there is anything that needs to be fixed.
+ *
+ * Looking for "//", "/./", "/.\0", "/../" or "/..\0".
+ *
+ * NB: does not consider that "." needs to be fixed, nor "./", and does not
+ * strip trailing "/".
+ */
+ while (1)
+ {
+ ch = *p++ ;
+
+ if (ch == '\0')
+ return ; /* scanned to end */
+
+ if (ch != '/') /* scanning for '/' */
+ continue ;
+
+ ch = *p++ ; /* get char after '/' */
+
+ if (ch == '\0')
+ return ; /* scanned to end */
+
+ if (ch == '/')
+ break ; /* second '/' */
+
+ if (ch != '.')
+ continue ; /* not "//" and not "/." */
+
+ ch = *p ; /* get char after "/." */
+
+ if ((ch == '/') || (ch == '\0'))
+ break ; /* found "/./" or "/.\0" */
+
+ if (ch != '.')
+ continue ; /* not "/..", either */
+
+ ch = *(p+1) ; /* get char after "/.." */
+
+ if ((ch == '/') || (ch == '\0'))
+ break ; /* found "/../" or "/..\0" */
+ } ;
+
+ /* Rats... there is something to be fixed.
+ *
+ * Enter the reduction loop ready to eat the first item.
+ */
+ --p ; /* back to second '/' or '.' */
+ q = p ; /* keep the first '/' */
+
+ eat = true ;
+
+ while (1)
+ {
+ if (eat)
+ {
+ eat = false ;
+
+ /* At this point:
+ *
+ * *p is '/', which is second of "//???"
+ * or (first) '.' of "./???", ".\0", "../???" or "..\0"
+ *
+ * *q is start of path or just after a '/'
+ *
+ * NB: after the first time through, p != q (or not necessarily).
+ */
+ qassert((*p == '/') || (*p == '.')) ;
+ if (*p == '.')
+ qassert((*(p+1) == '\0') || (*(p+1) == '/') || (*(p+1) == '.')) ;
+
+ qassert((q == sp) || (*(q-1) == '/')) ;
+
+ if ((*p == '.') && (*(p+1) == '.'))
+ {
+ char* sq = q ; /* in case find leading '~' */
+
+ qassert((*(p+2) == '\0') || (*(p+2) == '/')) ;
+
+ /* Deal with "../???" or "..\0"
+ *
+ * Before can strip the "..", have to verify that we have
+ * something to step back over.
+ *
+ * Can eat the ".." unless : is at start of path
+ * have nothing before the '/'
+ * (only) have '/' before the '/'
+ * only have "." before the '/'
+ * only have ".." before the '/'
+ * have "/.." before the '/'
+ *
+ * Note: can only have '/' before the '/' at the start.
+ *
+ * If cannot eat the '..', go back to the top of the loop, with
+ * eat == false, and process as an ordinary item.
+ */
+ if (q <= (sp + 1))
+ continue ; /* at start or
+ * nothing before the '/' */
+ if (*(q-2) == '/')
+ continue ; /* '/' before the '/' */
+
+ if (*(q-2) == '.')
+ {
+ /* Have '.' before the '/' */
+ if (q == (sp + 2))
+ continue ; /* only '.' before the '/' */
+
+ if (*(q-3) == '.')
+ {
+ /* Have ".." before the '/' */
+ if (q == (sp + 3))
+ continue ; /* only ".." before the '/' */
+
+ if (*(q-4) == '/')
+ continue ; /* "/.." before the '/' */
+ } ;
+ } ;
+
+ /* Eat the preceding item, including the final '/'
+ *
+ * *p == "../" or "..\0" to eat
+ * *q == just after '/' at end of item to eat
+ */
+ do /* starts with q just past '/' */
+ --q ;
+ while ((q > sp) && (*(q-1) != '/')) ;
+
+ /* Very special case: if have reduced the path to nothing,
+ * but path started with '~' then need to undo everything,
+ * and treate the '..' as ordinary item.
+ *
+ * Copes with ~fred/.. !!
+ */
+ if ((q == sp) && (*q == '~'))
+ {
+ q = sq ; /* put back item */
+ continue ; /* keep the '..' */
+ } ;
+
+ /* Eat the '..' and stop now if nothing more to come.
+ */
+ p += 2 ;
+
+ if (*p == '\0')
+ break ;
+
+ qassert(*p == '/') ;
+ } ;
+
+ /* Now discard '.' or '/' and any number of following '/'.
+ *
+ * *p == the '.' or first '/' to be discarded
+ *
+ * Ends up at first character after a '/' -- which may be anything,
+ * including a possible '.'
+ */
+ do
+ ++p ;
+ while (*p == '/') ;
+ }
+ else
+ {
+ /* Ordinary item (or '..') to copy across -- eat == false.
+ *
+ * Copying stuff, until get to '\0' or copy a '/'.
+ */
+ do
+ {
+ ch = *p ;
+
+ if (ch == '\0')
+ break ;
+
+ ++p ;
+ *q++ = ch ;
+ }
+ while (ch != '/') ;
+ } ;
+
+ /* Have processed to '\0' and/or just past a '/'
+ *
+ * Decide whether to continue, and if so whether to eat what follows
+ * or copy it across.
+ */
+ ch = *p ;
+
+ if (ch == '\0')
+ break ;
+
+ qassert(!eat) ;
+
+ if (ch == '/')
+ eat = true ; /* second '/' after '/' */
+ else if (ch == '.')
+ {
+ ch = *(p+1) ;
+ if ((ch == '/') || (ch == '\0'))
+ eat = true ; /* "./???" or ".\0" after '/' */
+ else if (ch == '.')
+ {
+ ch = *(p+2) ;
+ if ((ch == '/') || (ch == '\0'))
+ eat = true ; /* "../???" or "..\0" after '/' */
+ } ;
+ } ;
+ } ;
+
+ /* Adjust the length and terminate
+ */
+ qs_set_len_nn(qp->path, q - sp) ; /* set the new length (shorter !) */
+} ;
+