aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2020-02-18 07:53:30 +0000
committerWerner Koch <[email protected]>2020-02-18 07:56:33 +0000
commit5742b8eaf3fa9cda3dfb6b3ad0fea7485fff1a12 (patch)
tree7bb1347295569e10184f65ccfb3aea8e44495275
parentw32: Support static link with -lws2_32. (diff)
downloadlibgpg-error-5742b8eaf3fa9cda3dfb6b3ad0fea7485fff1a12.tar.gz
libgpg-error-5742b8eaf3fa9cda3dfb6b3ad0fea7485fff1a12.zip
core: Add gpgrt_fnameconcat and gpgrt_absfnameconcat.
* src/gpg-error.h.in (gpgrt_fnameconcat): New. (gpgrt_absfnameconcat): New. * src/visibility.c (gpgrt_fnameconcat, gpgrt_absfnameconcat): New. * src/stringutils.c: New file. (_gpgrt_vfnameconcat): New. (_gpgrt_fnameconcat, _gpgrt_absfnameconcat): New. * src/gpg-error.def.in: Add new functions. * src/gpg-error.vers: Ditto. * src/sysutils.c: Include pwd.h. (_gpgrt_getpwdir): New. * configure.ac: Test for pwd.h, getpwnam, getpwuid, and their _r variants. * src/Makefile.am (libgpg_error_la_SOURCES): Add new file. * tests/t-stringutils.c: New. * tests/t-common.h (xmalloc, xstrdup, xfree): New. (die): Kludge to avoid compiler warnings. -- The new functions are based on the code of make_filename from GnuPG. They have been written by me ages ago with only minor modifications by David Shaw. I re-license them from LGPL-3.0+ OR GPL-2.0+ to LGPL-2.1-or-later. Signed-off-by: Werner Koch <[email protected]>
-rw-r--r--NEWS7
-rw-r--r--configure.ac4
-rw-r--r--src/Makefile.am1
-rw-r--r--src/gpg-error.def.in3
-rw-r--r--src/gpg-error.h.in6
-rw-r--r--src/gpg-error.vers3
-rw-r--r--src/gpgrt-int.h11
-rw-r--r--src/stringutils.c224
-rw-r--r--src/sysutils.c39
-rw-r--r--src/visibility.c27
-rw-r--r--src/visibility.h4
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/t-common.h29
-rw-r--r--tests/t-stringutils.c282
14 files changed, 639 insertions, 3 deletions
diff --git a/NEWS b/NEWS
index f3469b1..a60af93 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,13 @@
Noteworthy changes in version 1.38 (unreleased) [C28/A28/R_]
-----------------------------------------------
+ * Interface changes relative to the 1.37 release:
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ gpgrt_fnameconcat NEW.
+ gpgrt_absfnameconcat NEW.
+
+ Release-info: https://dev.gnupg.org/T
+
Noteworthy changes in version 1.37 (2020-02-07) [C28/A28/R0]
-----------------------------------------------
diff --git a/configure.ac b/configure.ac
index a92143c..ceace2d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -190,7 +190,7 @@ AM_GNU_GETTEXT([external])
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h locale.h stdint.h sys/select.h sys/time.h \
- signal.h poll.h])
+ signal.h poll.h pwd.h])
AC_FUNC_STRERROR_R
case "${host_os}" in
@@ -213,7 +213,7 @@ AC_SUBST(INSTALLSHELLPATH)
AC_FUNC_FORK
AC_CHECK_FUNCS([flockfile vasprintf mmap rand strlwr stpcpy setenv stat \
- getrlimit ])
+ getrlimit getpwnam getpwuid getpwnam_r getpwuid_r ])
#
diff --git a/src/Makefile.am b/src/Makefile.am
index e9c057f..336fe2a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -198,6 +198,7 @@ libgpg_error_la_SOURCES = gettext.h $(arch_sources) \
strsource.c strerror.c code-to-errno.c code-from-errno.c \
visibility.c visibility.h \
sysutils.c \
+ stringutils.c \
syscall-clamp.c \
logging.c \
b64dec.c b64enc.c \
diff --git a/src/gpg-error.def.in b/src/gpg-error.def.in
index 0a584c4..4e3b5d7 100644
--- a/src/gpg-error.def.in
+++ b/src/gpg-error.def.in
@@ -229,4 +229,7 @@ EXPORTS
gpgrt_add_emergency_cleanup @174
gpgrt_abort @175
+ gpgrt_fnameconcat @178
+ gpgrt_absfnameconcat @179
+
;; end of file with public symbols for Windows.
diff --git a/src/gpg-error.h.in b/src/gpg-error.h.in
index d118e90..470021d 100644
--- a/src/gpg-error.h.in
+++ b/src/gpg-error.h.in
@@ -1309,6 +1309,12 @@ void gpgrt_set_fixed_string_mapper (const char *(*f)(const char*));
* numbering scheme a LEVEL of 3 is suitable; see the manual. */
int gpgrt_cmp_version (const char *a, const char *b, int level);
+/* Construct a filename from the NULL terminated list of parts. Tilde
+ * expansion is done for the first argument. The caller must release
+ * the result using gpgrt_free; on error ERRNO is set and NULL
+ * returned. The second function returns an absolute filename. */
+char *gpgrt_fnameconcat (const char *first, ...) GPGRT_ATTR_SENTINEL(0);
+char *gpgrt_absfnameconcat (const char *first, ...) GPGRT_ATTR_SENTINEL(0);
#ifdef __cplusplus
diff --git a/src/gpg-error.vers b/src/gpg-error.vers
index eef4cbc..594342b 100644
--- a/src/gpg-error.vers
+++ b/src/gpg-error.vers
@@ -199,6 +199,9 @@ GPG_ERROR_1.0 {
gpgrt_add_emergency_cleanup;
gpgrt_abort;
+ gpgrt_fnameconcat;
+ gpgrt_absfnameconcat;
+
local:
*;
};
diff --git a/src/gpgrt-int.h b/src/gpgrt-int.h
index 718b62c..97f0533 100644
--- a/src/gpgrt-int.h
+++ b/src/gpgrt-int.h
@@ -787,6 +787,17 @@ gpg_err_code_t _gpgrt_chdir (const char *name);
/* Return the current WD as a malloced string. */
char *_gpgrt_getcwd (void);
+/* Return the home directory of user NAME. */
+char *_gpgrt_getpwdir (const char *name);
+
+/* Expand and concat file name parts. */
+char *_gpgrt_vfnameconcat (int want_abs, const char *first_part,
+ va_list arg_ptr);
+char *_gpgrt_fnameconcat (const char *first_part,
+ ... ) GPGRT_ATTR_SENTINEL(0);
+char *_gpgrt_absfnameconcat (const char *first_part,
+ ... ) GPGRT_ATTR_SENTINEL(0);
+
/*
* Platform specific functions (Windows)
diff --git a/src/stringutils.c b/src/stringutils.c
new file mode 100644
index 0000000..d92398d
--- /dev/null
+++ b/src/stringutils.c
@@ -0,0 +1,224 @@
+/* stringutils.c - String helper functions.
+ * Copyright (C) 1997, 2014 Werner Koch
+ * Copyright (C) 2020 g10 Code GmbH
+ *
+ * This file is part of libgpg-error.
+ *
+ * libgpg-error is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * libgpg-error 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#ifdef HAVE_STAT
+# include <sys/stat.h>
+#endif
+#include <sys/types.h>
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
+
+#include "gpgrt-int.h"
+
+
+/* Helper for _gpgrt_fnameconcat. The additional flag WANT_ABS tells
+ * whether an absolute file name is requested. */
+char *
+_gpgrt_vfnameconcat (int want_abs, const char *first_part, va_list arg_ptr)
+{
+ const char *argv[32];
+ int argc;
+ size_t n;
+ int skip = 1; /* Characters to skip from FIRST_PART. */
+ char *home_buffer = NULL;
+ char *name, *home, *p;
+
+ /* Put all args into an array becuase we need to scan them twice. */
+ n = strlen (first_part) + 1;
+ argc = 0;
+ while ((argv[argc] = va_arg (arg_ptr, const char *)))
+ {
+ n += strlen (argv[argc]) + 1;
+ if (argc >= DIM (argv)-1)
+ {
+ _gpg_err_set_errno (EINVAL);
+ return NULL;
+ }
+ argc++;
+ }
+ n++;
+
+ home = NULL;
+ if (*first_part == '~')
+ {
+ if (first_part[1] == '/' || !first_part[1])
+ {
+ /* This is the "~/" or "~" case. */
+ home_buffer = _gpgrt_getenv ("HOME");
+ if (!home_buffer)
+ home_buffer = _gpgrt_getpwdir (NULL);
+ home = home_buffer;
+ if (home && *home)
+ n += strlen (home);
+ }
+ else
+ {
+ /* This is the "~username/" or "~username" case. */
+ char *user;
+
+ user = _gpgrt_strdup (first_part+1);
+ if (!user)
+ return NULL;
+
+ p = strchr (user, '/');
+ if (p)
+ *p = 0;
+ skip = 1 + strlen (user);
+
+ home = home_buffer = _gpgrt_getpwdir (user);
+ xfree (user);
+ if (home)
+ n += strlen (home);
+ else
+ skip = 1;
+ }
+ }
+
+ name = xtrymalloc (n);
+ if (!name)
+ {
+ _gpgrt_free (home_buffer);
+ return NULL;
+ }
+
+ if (home)
+ p = stpcpy (stpcpy (name, home), first_part + skip);
+ else
+ p = stpcpy (name, first_part);
+
+ xfree (home_buffer);
+ home_buffer = NULL;
+
+ for (argc=0; argv[argc]; argc++)
+ {
+ /* Avoid a leading double slash if the first part was "/". */
+ if (!argc && name[0] == '/' && !name[1])
+ p = stpcpy (p, argv[argc]);
+ else
+ p = stpcpy (stpcpy (p, "/"), argv[argc]);
+ }
+
+ if (want_abs)
+ {
+#ifdef HAVE_W32_SYSTEM
+ p = strchr (name, ':');
+ if (p)
+ p++;
+ else
+ p = name;
+#else
+ p = name;
+#endif
+ if (*p != '/'
+#ifdef HAVE_W32_SYSTEM
+ && *p != '\\'
+#endif
+ )
+ {
+ home = _gpgrt_getcwd ();
+ if (!home)
+ {
+ xfree (name);
+ return NULL;
+ }
+
+ n = strlen (home) + 1 + strlen (name) + 1;
+ home_buffer = xtrymalloc (n);
+ if (!home_buffer)
+ {
+ xfree (home);
+ xfree (name);
+ return NULL;
+ }
+
+ if (p == name)
+ p = home_buffer;
+ else /* Windows case. */
+ {
+ memcpy (home_buffer, p, p - name + 1);
+ p = home_buffer + (p - name + 1);
+ }
+
+ /* Avoid a leading double slash if the cwd is "/". */
+ if (home[0] == '/' && !home[1])
+ strcpy (stpcpy (p, "/"), name);
+ else
+ strcpy (stpcpy (stpcpy (p, home), "/"), name);
+
+ xfree (home);
+ xfree (name);
+ name = home_buffer;
+ /* Let's do a simple compression to catch the common case of
+ * a trailing "/.". */
+ n = strlen (name);
+ if (n > 2 && name[n-2] == '/' && name[n-1] == '.')
+ name[n-2] = 0;
+ }
+ }
+
+#ifdef HAVE_W32_SYSTEM
+ for (p=name; *p; p++)
+ if (*p == '\\')
+ *p = '/';
+#endif
+ return name;
+}
+
+
+/* Construct a filename from the NULL terminated list of parts. Tilde
+ * expansion is done for the first argument. The caller must release
+ * the result using gpgrt_free; on error ERRNO is set and NULL
+ * returned. */
+char *
+_gpgrt_fnameconcat (const char *first_part, ... )
+{
+ va_list arg_ptr;
+ char *result;
+
+ va_start (arg_ptr, first_part);
+ result = _gpgrt_vfnameconcat (0, first_part, arg_ptr);
+ va_end (arg_ptr);
+ return result;
+}
+
+
+/* Construct a filename from the NULL terminated list of parts. Tilde
+ * expansion is done for the first argument. The caller must release
+ * the result using gpgrt_free; on error ERRNO is set and NULL
+ * returned. This version returns an absolute filename. */
+char *
+_gpgrt_absfnameconcat (const char *first_part, ... )
+{
+ va_list arg_ptr;
+ char *result;
+
+ va_start (arg_ptr, first_part);
+ result = _gpgrt_vfnameconcat (1, first_part, arg_ptr);
+ va_end (arg_ptr);
+ return result;
+}
diff --git a/src/sysutils.c b/src/sysutils.c
index bc31d92..6bdd76f 100644
--- a/src/sysutils.c
+++ b/src/sysutils.c
@@ -32,6 +32,9 @@
#endif
#include <sys/types.h>
#include <fcntl.h>
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
#include "gpgrt-int.h"
@@ -337,3 +340,39 @@ _gpgrt_getcwd (void)
#endif
}
}
+
+
+/* Get the standard home directory for user NAME. If NAME is NULL the
+ * directory for the current user is retruned. Caller must release
+ * the returned string. */
+char *
+_gpgrt_getpwdir (const char *name)
+{
+ char *result = NULL;
+#ifdef HAVE_PWD_H
+ struct passwd *pwd = NULL;
+
+ if (name)
+ {
+#ifdef HAVE_GETPWNAM
+ /* Fixme: We should use getpwnam_r if available. */
+ pwd = getpwnam (name);
+#endif
+ }
+ else
+ {
+#ifdef HAVE_GETPWUID
+ /* Fixme: We should use getpwuid_r if available. */
+ pwd = getpwuid (getuid());
+#endif
+ }
+ if (pwd)
+ {
+ result = _gpgrt_strdup (pwd->pw_dir);
+ }
+#else /*!HAVE_PWD_H*/
+ /* No support at all. */
+ (void)name;
+#endif /*HAVE_PWD_H*/
+ return result;
+}
diff --git a/src/visibility.c b/src/visibility.c
index d754032..5f88aad 100644
--- a/src/visibility.c
+++ b/src/visibility.c
@@ -1180,6 +1180,33 @@ gpgrt_cmp_version (const char *a, const char *b, int level)
+/* String utilities. */
+char *
+gpgrt_fnameconcat (const char *first, ... )
+{
+ va_list arg_ptr;
+ char *result;
+
+ va_start (arg_ptr, first);
+ result = _gpgrt_vfnameconcat (0, first, arg_ptr);
+ va_end (arg_ptr);
+ return result;
+}
+
+char *
+gpgrt_absfnameconcat (const char *first, ... )
+{
+ va_list arg_ptr;
+ char *result;
+
+ va_start (arg_ptr, first);
+ result = _gpgrt_vfnameconcat (1, first, arg_ptr);
+ va_end (arg_ptr);
+ return result;
+}
+
+
+
/* For consistency reasons we use function wrappers also for Windows
* specific function despite that they are technically not needed. */
#ifdef HAVE_W32_SYSTEM
diff --git a/src/visibility.h b/src/visibility.h
index 28038d0..192f733 100644
--- a/src/visibility.h
+++ b/src/visibility.h
@@ -216,6 +216,10 @@ MARK_VISIBLE (gpgrt_set_usage_outfnc);
MARK_VISIBLE (gpgrt_cmp_version);
+MARK_VISIBLE (gpgrt_fnameconcat);
+MARK_VISIBLE (gpgrt_absfnameconcat);
+
+
#undef MARK_VISIBLE
#else /*!_GPGRT_INCL_BY_VISIBILITY_C*/
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ea02da1..39ca241 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -28,7 +28,7 @@ endif
gpg_error_lib = ../src/libgpg-error.la
TESTS = t-version t-strerror t-syserror t-lock t-printf t-poll t-b64 \
- t-argparse t-logging
+ t-argparse t-logging t-stringutils
AM_CPPFLAGS = -I$(top_builddir)/src $(extra_includes)
diff --git a/tests/t-common.h b/tests/t-common.h
index e70e04d..db496e2 100644
--- a/tests/t-common.h
+++ b/tests/t-common.h
@@ -39,6 +39,33 @@ static void fail (const char *format, ...) GPGRT_ATTR_PRINTF(1,2);
static void show (const char *format, ...) GPGRT_ATTR_PRINTF(1,2);
+static void *
+xmalloc (size_t n)
+{
+ char *p = gpgrt_malloc (n);
+ if (!p)
+ die ("out of core\n");
+ return p;
+}
+
+static char *
+xstrdup (const char *s)
+{
+ char *p = gpgrt_strdup (s);
+ if (!p)
+ die ("out of core\n");
+ return p;
+}
+
+static void
+xfree (void *p)
+{
+ if (p)
+ gpgrt_free (p);
+}
+
+
+
static void
die (const char *format, ...)
{
@@ -57,6 +84,8 @@ die (const char *format, ...)
#ifdef HAVE_FLOCKFILE
funlockfile (stderr);
#endif
+ xfree (xstrdup ("")); /* To avoid compiler warnings. */
+ xfree (xmalloc (16)); /* To avoid compiler warnings. */
exit (1);
}
diff --git a/tests/t-stringutils.c b/tests/t-stringutils.c
new file mode 100644
index 0000000..01094bf
--- /dev/null
+++ b/tests/t-stringutils.c
@@ -0,0 +1,282 @@
+/* t-stringutils.c - Check some string utilities
+ * Copyright (C) 2020 g10 Code GmbH
+ *
+ * This file is part of Libgpg-error.
+ *
+ * Libgpg-error is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgpg-error 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#ifdef HAVE_STAT
+# include <sys/stat.h>
+#endif
+#include <sys/types.h>
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
+#include <errno.h>
+
+#define PGM "t-stringutils"
+#include "t-common.h"
+
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+
+ switch (level)
+ {
+ case 9: p = "LGPL-2.1-or-later"; break;
+ case 11: p = PGM; break;
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+const char *
+mygethome (void)
+{
+ static char *home_buffer;
+
+ if (!home_buffer)
+ {
+ char *home = getenv("HOME");
+
+ if(home)
+ home_buffer = xstrdup (home);
+#if defined(HAVE_GETPWUID) && defined(HAVE_PWD_H)
+ else
+ {
+ struct passwd *pwd;
+
+ pwd = getpwuid (getuid());
+ if (pwd)
+ home_buffer = xstrdup (pwd->pw_dir);
+ }
+#endif
+ }
+ return home_buffer;
+}
+
+
+static char *
+mygetcwd (void)
+{
+ char *buffer;
+ size_t size = 100;
+
+ for (;;)
+ {
+ buffer = xmalloc (size+1);
+ if (getcwd (buffer, size) == buffer)
+ return buffer;
+ xfree (buffer);
+ if (errno != ERANGE)
+ die ("error getting current cwd: %s\n", strerror (errno));
+ size *= 2;
+ }
+}
+
+
+static void
+check_fnameconcat (void)
+{
+ char *out;
+ const char *home = mygethome ();
+ size_t homelen = home? strlen (home):0;
+
+ out = gpgrt_fnameconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", NULL);
+ if (out)
+ fail ("fnameconcat succeeded but should not at line %d\n", __LINE__);
+ else if (errno != EINVAL)
+ fail ("fnameconcat return wrong error at line %d\n", __LINE__);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", NULL);
+ if (out)
+ fail ("fnameconcat succeeded but should not at line %d\n", __LINE__);
+ else if (errno != EINVAL)
+ fail ("fnameconcat return wrong error at line %d\n", __LINE__);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "1", "2", NULL);
+ if (!out || strcmp (out,
+ "1/2/3/4/5/6/7/8/9/10/"
+ "1/2/3/4/5/6/7/8/9/10/"
+ "1/2/3/4/5/6/7/8/9/10/"
+ "1/2"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("foo", "~/bar", "baz/cde", NULL);
+ if (!out || strcmp (out, "foo/~/bar/baz/cde"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("foo", "~/bar", "baz/cde/", NULL);
+ if (!out || strcmp (out, "foo/~/bar/baz/cde/"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("/foo", "~/bar", "baz/cde/", NULL);
+ if (!out || strcmp (out, "/foo/~/bar/baz/cde/"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("//foo", "~/bar", "baz/cde/", NULL);
+ if (!out || strcmp (out, "//foo/~/bar/baz/cde/"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("", "~/bar", "baz/cde", NULL);
+ if (!out || strcmp (out, "/~/bar/baz/cde"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("~/foo", "bar", NULL);
+ if (!out)
+ fail ("fnameconcat failed at line %d\n", __LINE__);
+ else if (home)
+ {
+ if (strlen (out) < homelen + 7)
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strncmp (out, home, homelen))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strcmp (out+homelen, "/foo/bar"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ }
+ else
+ {
+ if (strcmp (out, "~/foo/bar"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ }
+ xfree (out);
+
+ out = gpgrt_fnameconcat ("~", "bar", NULL);
+ if (!out)
+ fail ("fnameconcat failed at line %d\n", __LINE__);
+ else if (home)
+ {
+ if (strlen (out) < homelen + 3)
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strncmp (out, home, homelen))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strcmp (out+homelen, "/bar"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ }
+ else
+ {
+ if (strcmp (out, "~/bar"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ }
+ xfree (out);
+}
+
+
+static void
+check_absfnameconcat (void)
+{
+ char *out;
+ char *cwd = mygetcwd ();
+ size_t cwdlen = strlen (cwd);
+
+ out = gpgrt_absfnameconcat ("foo", "bar", NULL);
+ if (!out)
+ fail ("fnameconcat failed at line %d\n", __LINE__);
+ else if (strlen (out) < cwdlen + 7)
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strncmp (out, cwd, cwdlen))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strcmp (out+cwdlen, "/foo/bar"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_absfnameconcat ("./foo", NULL);
+ if (!out)
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strlen (out) < cwdlen + 5)
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strncmp (out, cwd, cwdlen))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strcmp (out+cwdlen, "/./foo"))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ out = gpgrt_absfnameconcat (".", NULL);
+ if (!out)
+ fail ("fnameconcat failed at line %d\n", __LINE__);
+ else if (strlen (out) < cwdlen)
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strncmp (out, cwd, cwdlen))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ else if (strcmp (out+cwdlen, ""))
+ fail ("fnameconcat failed at line %d (out=%s)\n", __LINE__, out);
+ xfree (out);
+
+ xfree (cwd);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ gpgrt_opt_t opts[] = {
+ ARGPARSE_x ('v', "verbose", NONE, 0, "Print more diagnostics"),
+ ARGPARSE_s_n('d', "debug", "Flyswatter"),
+ ARGPARSE_end()
+ };
+ gpgrt_argparse_t pargs = { &argc, &argv, 0 };
+
+ gpgrt_set_strusage (my_strusage);
+ gpgrt_log_set_prefix (gpgrt_strusage (11), GPGRT_LOG_WITH_PREFIX);
+
+ while (gpgrt_argparse (NULL, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case 'v': verbose++; break;
+ case 'd': debug++; break;
+ default : pargs.err = ARGPARSE_PRINT_ERROR; break;
+ }
+ }
+ gpgrt_argparse (NULL, &pargs, NULL);
+
+ show ("testing string utilities\n");
+
+ check_fnameconcat ();
+ check_absfnameconcat ();
+
+ show ("testing string utilities finished\n");
+ return !!errorcount;
+}