* lib/filenamecat-lgpl.c (longest_relative_suffix): Remove. (mfile_name_concat): Always make BASE a suffix of the result, as cp expects this. To implement this, separate with '.' instead of '/' in some rare cases. Clarify spec to say ./BASE not BASE. * tests/test-filenamecat.c (main): Adjust tests to match current behavior. Check that BASE_IN_RESULT points to a copy of BASE and is a suffix of the resultk, and that DIR is a prefix of the result that is no longer than the prefix indicated by BASE_IN_RESULT. --- ChangeLog | 13 +++++++++++ lib/filenamecat-lgpl.c | 57 ++++++++++++++++++++++++------------------------ lib/filenamecat.c | 4 ++-- tests/test-filenamecat.c | 27 ++++++++++++++++++++--- 4 files changed, 67 insertions(+), 34 deletions(-)
diff --git a/ChangeLog b/ChangeLog index 13e868aca..e4801bd17 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2018-01-14 Paul Eggert <egg...@cs.ucla.edu> + + filenamecat: make base a suffix of result + * lib/filenamecat-lgpl.c (longest_relative_suffix): Remove. + (mfile_name_concat): Always make BASE a suffix of the result, as + cp expects this. To implement this, separate with '.' instead of + '/' in some rare cases. Clarify spec to say ./BASE not BASE. + * tests/test-filenamecat.c (main): Adjust tests to match + current behavior. Check that BASE_IN_RESULT points to + a copy of BASE and is a suffix of the resultk, and that DIR + is a prefix of the result that is no longer than the prefix + indicated by BASE_IN_RESULT. + 2018-01-04 Mathieu Lirzin <m...@gnu.org> update-copyright: Handle use of © diff --git a/lib/filenamecat-lgpl.c b/lib/filenamecat-lgpl.c index 452d3b95d..f50ecc138 100644 --- a/lib/filenamecat-lgpl.c +++ b/lib/filenamecat-lgpl.c @@ -31,55 +31,54 @@ # define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N))) #endif -/* Return the longest suffix of F that is a relative file name. - If it has no such suffix, return the empty string. */ - -static char const * _GL_ATTRIBUTE_PURE -longest_relative_suffix (char const *f) -{ - for (f += FILE_SYSTEM_PREFIX_LEN (f); ISSLASH (*f); f++) - continue; - return f; -} - -/* Concatenate two file name components, DIR and ABASE, in +/* Concatenate two file name components, DIR and BASE, in newly-allocated storage and return the result. The resulting file name F is such that the commands "ls F" and "(cd - DIR; ls BASE)" refer to the same file, where BASE is ABASE with any - file system prefixes and leading separators removed. - Arrange for a directory separator if necessary between DIR and BASE - in the result, removing any redundant separators. + DIR; ls ./BASE)" refer to the same file. If necessary, put + a separator between DIR and BASE in the result. Typically this + separator is "/", but in rare cases it might be ".". In any case, if BASE_IN_RESULT is non-NULL, set - *BASE_IN_RESULT to point to the copy of ABASE in the returned - concatenation. However, if ABASE begins with more than one slash, - set *BASE_IN_RESULT to point to the sole corresponding slash that - is copied into the result buffer. + *BASE_IN_RESULT to point to the copy of BASE at the end of the + returned concatenation. Return NULL if malloc fails. */ char * -mfile_name_concat (char const *dir, char const *abase, char **base_in_result) +mfile_name_concat (char const *dir, char const *base, char **base_in_result) { char const *dirbase = last_component (dir); size_t dirbaselen = base_len (dirbase); size_t dirlen = dirbase - dir + dirbaselen; - size_t needs_separator = (dirbaselen && ! ISSLASH (dirbase[dirbaselen - 1])); - - char const *base = longest_relative_suffix (abase); size_t baselen = strlen (base); - - char *p_concat = malloc (dirlen + needs_separator + baselen + 1); + char sep = '\0'; + if (dirbaselen) + { + /* DIR is not a file system root, so separate with / if needed. */ + if (! ISSLASH (dir[dirlen - 1]) && ! ISSLASH (*base)) + sep = '/'; + } + else if (ISSLASH (*base)) + { + /* DIR is a file system root and BASE begins with a slash, so + separate with ".". For example, if DIR is "/" and BASE is + "/foo" then return "/./foo", as "//foo" would be wrong on + some POSIX systems. A fancier algorithm could omit "." in + some cases but is not worth the trouble. */ + sep = '.'; + } + + char *p_concat = malloc (dirlen + (sep != '\0') + baselen + 1); char *p; if (p_concat == NULL) return NULL; p = mempcpy (p_concat, dir, dirlen); - *p = DIRECTORY_SEPARATOR; - p += needs_separator; + *p = sep; + p += sep != '\0'; if (base_in_result) - *base_in_result = p - IS_ABSOLUTE_FILE_NAME (abase); + *base_in_result = p; p = mempcpy (p, base, baselen); *p = '\0'; diff --git a/lib/filenamecat.c b/lib/filenamecat.c index 5f5e24465..cad459586 100644 --- a/lib/filenamecat.c +++ b/lib/filenamecat.c @@ -32,9 +32,9 @@ "memory exhausted" condition and exit. */ char * -file_name_concat (char const *dir, char const *abase, char **base_in_result) +file_name_concat (char const *dir, char const *base, char **base_in_result) { - char *p = mfile_name_concat (dir, abase, base_in_result); + char *p = mfile_name_concat (dir, base, base_in_result); if (p == NULL) xalloc_die (); return p; diff --git a/tests/test-filenamecat.c b/tests/test-filenamecat.c index f9f121712..17ac670da 100644 --- a/tests/test-filenamecat.c +++ b/tests/test-filenamecat.c @@ -22,6 +22,7 @@ #include "filenamecat.h" #include <stdbool.h> +#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -38,11 +39,11 @@ main (int argc _GL_UNUSED, char *argv[]) {"a", "/b", "a/b"}, {"/", "b", "/b"}, - {"/", "/b", "/b"}, - {"/", "/", "/"}, + {"/", "/b", "/./b"}, /* This result could be shorter. */ + {"/", "/", "/./"}, /* This result could be shorter. */ {"a", "/", "a/"}, /* this might deserve a diagnostic */ {"/a", "/", "/a/"}, /* this might deserve a diagnostic */ - {"a", "//b", "a/b"}, + {"a", "//b", "a//b"}, {"", "a", "a"}, /* this might deserve a diagnostic */ }; unsigned int i; @@ -53,11 +54,31 @@ main (int argc _GL_UNUSED, char *argv[]) char *base_in_result; char const *const *t = tests[i]; char *res = file_name_concat (t[0], t[1], &base_in_result); + ptrdiff_t prefixlen = base_in_result - res; + size_t t0len = strlen (t[0]); + size_t reslen = strlen (res); if (strcmp (res, t[2]) != 0) { fprintf (stderr, "test #%u: got %s, expected %s\n", i, res, t[2]); fail = true; } + if (strcmp (t[1], base_in_result) != 0) + { + fprintf (stderr, "test #%u: base %s != base_in_result %s\n", + i, t[1], base_in_result); + fail = true; + } + if (! (0 <= prefixlen && prefixlen <= reslen)) + { + fprintf (stderr, "test #%u: base_in_result is not in result\n", i); + fail = true; + } + if (reslen < t0len || memcmp (res, t[0], t0len) != 0) + { + fprintf (stderr, "test #%u: %s is not a prefix of %s\n", + i, t[0], res); + fail = true; + } } exit (fail ? EXIT_FAILURE : EXIT_SUCCESS); } -- 2.14.3