diff --git a/include/xbps.h.in b/include/xbps.h.in index b4928512..849142c9 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2185,6 +2185,60 @@ char *xbps_pubkey2fp(struct xbps_handle *xhp, xbps_data_t pubkey); */ char *xbps_sanitize_path(const char *src); +/** + * Turns the path in \a path into the shortest path equivalent to \a path + * by purely lexical processing. + * + * @param[in,out] path The path to clean. + * + * @return The length of the path or -1 on error. + */ +ssize_t xbps_path_clean(char *path); + +/** + * Returns the relative path from \a from to \a to. + * + * @param[out] dst Destination buffer to store result. + * @param[in] len Length of \a dst. + * @param[in] from The base path. + * @param[in] to The path that becomes relative to \a from. + * + * @return The length of the path or -1 on error. + */ +ssize_t xbps_path_rel(char *dst, size_t len, const char *from, const char *to); + +/** + * Joins multiple path components into the \a dst buffer. + * + * The last argument has to be (char *)NULL. + * + * @param[out] dst Destination buffer to store result. + * @param[in] len Length of \a dst. + * + * @return The length of the path or -1 on error. + */ +ssize_t xbps_path_join(char *dst, size_t len, ...); + +/** + * Adds \a rootdir and \a path to the \a dst buffer. + * + * @param[out] dst Destination buffer to store result. + * @param[in] len Length of \a dst. + * + * @return The length of the path or -1 on error. + */ +ssize_t xbps_path_append(char *dst, size_t len, const char *suffix); + +/** + * Adds \a rootdir and \a path to the \a dst buffer. + * + * @param[out] dst Destination buffer to store result. + * @param[in] len Length of \a dst. + * + * @return The length of the path or -1 on error. + */ +ssize_t xbps_path_prepend(char *dst, size_t len, const char *prefix); + /** * Returns a sanitized target file of \a path without the rootdir component. * diff --git a/lib/Makefile b/lib/Makefile index 2006aea3..2c932211 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -43,7 +43,7 @@ OBJS += transaction_files.o transaction_fetch.o OBJS += pubkey2fp.o package_fulldeptree.o OBJS += download.o initend.o pkgdb.o OBJS += plist.o plist_find.o plist_match.o archive.o -OBJS += plist_remove.o plist_fetch.o util.o util_hash.o +OBJS += plist_remove.o plist_fetch.o util.o util_path.o util_hash.o OBJS += repo.o repo_pkgdeps.o repo_sync.o OBJS += rpool.o cb_util.o proplib_wrapper.o OBJS += package_alternatives.o diff --git a/lib/util_path.c b/lib/util_path.c new file mode 100644 index 00000000..c7ba7c7c --- /dev/null +++ b/lib/util_path.c @@ -0,0 +1,291 @@ +/*- + * Copyright (c) 2015-2019 Juan Romero Pardines. + * Copyright (c) 2020 Duncan Overbruck . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*- + * xbps_path_clean is based on the go filepath.Clean function: + * - https://github.com/golang/go/blob/cfe2ab42/src/path/filepath/path.go#L88 + * + * Copyright (c) 2009 The Go Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "xbps_api_impl.h" + +ssize_t +xbps_path_clean(char *dst) +{ + char buf[PATH_MAX]; + const char *p = buf; + const char *dotdot = dst; + char *d = dst; + bool rooted = *dst == '/'; + + if (xbps_strlcpy(buf, dst, sizeof buf) >= sizeof buf) + return -1; + + if (rooted) { + *d++ = '/'; + p++; + dotdot++; + } + + for (; *p;) { + switch (*p) { + /* empty path element */ + case '/': p++; break; + case '.': + if (p[1] == '\0' || p[1] == '/') { + /* . element */ + p++; + continue; + } else if (p[1] == '.' && (p[2] == '\0' || p[2] == '/')) { + p += 2; + /* .. element */ + if (d > dotdot) { + /* can backtrack */ + d--; + for (; d > dotdot && *d != '/'; d--) + ; + } else if (!rooted) { + /* cannot backtrack, but not rooted, + * append .. element. */ + if (d > dst) + *d++ = '/'; + *d++ = '.'; + *d++ = '.'; + dotdot = d; + } + continue; + } + /* normal path element starting with . */ + /* FALLTHROUGH */ + default: + if (d > dst+(rooted ? 1 : 0)) + *d++ = '/'; + for (; *p && *p != '/'; p++) + *d++ = *p; + } + } + + /* Turn empty string into "." */ + if (d == dst) + *d++ = '.'; + + *d = '\0'; + return (d-dst); +} + +ssize_t +xbps_path_rel(char *dst, size_t dstlen, const char *from, const char *to) +{ + char frombuf[PATH_MAX], tobuf[PATH_MAX]; + const char *fromp = frombuf, *top = tobuf, *suffix = tobuf; + size_t len = 0; + int up = -1; + + *dst = '\0'; + + if (xbps_strlcpy(frombuf, from, sizeof frombuf) >= sizeof frombuf || + xbps_strlcpy(tobuf, to, sizeof tobuf) >= sizeof tobuf) + return -1; + + if (xbps_path_clean(frombuf) == -1 || xbps_path_clean(tobuf) == -1) + return -1; + + for (; *fromp == *top && *to; fromp++, top++) + if (*top == '/') + suffix = top; + + for (up = -1, fromp--; fromp && *fromp; fromp = strchr(fromp+1, '/'), up++) + ; + + while (up--) { + for (const char *x = "../"; *x; x++) { + if (len+1 < dstlen) + dst[len] = *x; + len++; + } + } + if (*suffix != '\0') { + for (suffix += 1; *suffix; suffix++) { + if (len+1 < dstlen) + dst[len] = *suffix; + len++; + } + } + + dst[len < dstlen ? len : dstlen - 1] = '\0'; + return len; +} + +static ssize_t +xbps_path_vjoin(char *dst, size_t dstlen, va_list ap) +{ + size_t len = 0; + const char *val; + *dst = '\0'; + + if ((val = va_arg(ap, const char *)) == NULL) + return 0; + + for (;;) { + size_t n; + if ((n = xbps_strlcat(dst+len, val, dstlen-len)) >= dstlen-len) + goto err; + len += n; + if ((val = va_arg(ap, const char *)) == NULL) + break; + if (len > 0 && dst[len-1] != '/') { + if (len+1 > dstlen) + goto err; + dst[len] = '/'; + dst[len+1] = '\0'; + len++; + } + if (len > 0 && *val == '/') + val++; + } + + return (ssize_t)len < 0 ? -1 : (ssize_t)len; +err: + errno = ENOBUFS; + return -1; +} + +ssize_t +xbps_path_join(char *dst, size_t dstlen, ...) +{ + ssize_t len; + va_list ap; + va_start(ap, dstlen); + len = xbps_path_vjoin(dst, dstlen, ap); + va_end(ap); + return len; +} + +ssize_t +xbps_path_append(char *dst, size_t dstlen, const char *suffix) +{ + size_t len = strlen(dst); + + if (*suffix == '\0') + goto out; + + if (*dst == '\0') { + if ((len = xbps_strlcpy(dst, suffix, dstlen)) >= dstlen) + goto err; + goto out; + } + + if (dst[len-1] != '/' && len+1 < dstlen) { + dst[len] = '/'; + dst[len+1] = '\0'; + } + if (*suffix == '/') + suffix++; + + if ((len = xbps_strlcat(dst, suffix, dstlen)) >= dstlen) + goto err; +out: + return (ssize_t)len < 0 ? -1 : (ssize_t)len; +err: + errno = ENOBUFS; + return -1; +} + +ssize_t +xbps_path_prepend(char *dst, size_t dstlen, const char *prefix) +{ + size_t len, prelen; + char *p = dst; + + len = strlen(dst); + + if (*prefix == '\0') + goto out; + + if (*dst == '\0') { + if ((len = xbps_strlcpy(dst, prefix, dstlen)) >= dstlen) + goto err; + goto out; + } + + prelen = strlen(prefix); + if (prefix[prelen-1] == '/') + prelen--; + + if (*dst == '/') { + len--; + p++; + } + + /* prefix + '/' + dst + '\0' */ + if (len+prelen+2 > dstlen) + goto err; + + memmove(dst+prelen+1, p, len); + + len += prelen+1; + + dst[prelen] = '/'; + + memcpy(dst, prefix, prelen); + + dst[len] = '\0'; +out: + return (ssize_t)len < 0 ? -1 : (ssize_t)len; +err: + errno = ENOBUFS; + return -1; +} diff --git a/tests/xbps/libxbps/Kyuafile b/tests/xbps/libxbps/Kyuafile index 880434ab..86a009d9 100644 --- a/tests/xbps/libxbps/Kyuafile +++ b/tests/xbps/libxbps/Kyuafile @@ -3,6 +3,7 @@ syntax("kyuafile", 1) test_suite("libxbps") include('util/Kyuafile') +include('util_path/Kyuafile') include('cmpver/Kyuafile') include('pkgpattern_match/Kyuafile') include('plist_match/Kyuafile') diff --git a/tests/xbps/libxbps/Makefile b/tests/xbps/libxbps/Makefile index ad070f65..ca361bc6 100644 --- a/tests/xbps/libxbps/Makefile +++ b/tests/xbps/libxbps/Makefile @@ -7,6 +7,7 @@ SUBDIRS += pkgpattern_match SUBDIRS += plist_match SUBDIRS += plist_match_virtual SUBDIRS += util +SUBDIRS += util_path SUBDIRS += find_pkg_orphans SUBDIRS += pkgdb SUBDIRS += config diff --git a/tests/xbps/libxbps/util_path/Kyuafile b/tests/xbps/libxbps/util_path/Kyuafile new file mode 100644 index 00000000..26aeea3d --- /dev/null +++ b/tests/xbps/libxbps/util_path/Kyuafile @@ -0,0 +1,5 @@ +syntax("kyuafile", 1) + +test_suite("libxbps") + +atf_test_program{name="util_path_test"} diff --git a/tests/xbps/libxbps/util_path/Makefile b/tests/xbps/libxbps/util_path/Makefile new file mode 100644 index 00000000..66741ac1 --- /dev/null +++ b/tests/xbps/libxbps/util_path/Makefile @@ -0,0 +1,8 @@ +TOPDIR = ../../../.. +-include $(TOPDIR)/config.mk + +TESTSSUBDIR = xbps/libxbps/util +TEST = util_path_test +EXTRA_FILES = Kyuafile + +include $(TOPDIR)/mk/test.mk diff --git a/tests/xbps/libxbps/util_path/main.c b/tests/xbps/libxbps/util_path/main.c new file mode 100644 index 00000000..fbd14b9a --- /dev/null +++ b/tests/xbps/libxbps/util_path/main.c @@ -0,0 +1,398 @@ +/*- + * Copyright (c) 2012-2014 Juan Romero Pardines. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *- + */ +#include +#include + +#include +#include + +ATF_TC(xbps_path_clean); + +ATF_TC_HEAD(xbps_path_clean, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_path_clean"); +} + +ATF_TC_BODY(xbps_path_clean, tc) +{ + char buf[PATH_MAX]; + ssize_t len; +#define CLEAN(a, b) \ + xbps_strlcpy(buf, a, sizeof (buf)); \ + len = xbps_path_clean(buf); \ + ATF_CHECK_EQ(len, sizeof (b)-1); \ + ATF_CHECK_STREQ(buf, b) + + /* Already clean */ + CLEAN("abc", "abc"); + CLEAN("abc/def", "abc/def"); + CLEAN("a/b/c", "a/b/c"); + CLEAN(".", "."); + CLEAN("..", ".."); + CLEAN("../..", "../.."); + CLEAN("../../abc", "../../abc"); + CLEAN("/abc", "/abc"); + CLEAN("/", "/"); + + /* Empty is current dir */ + CLEAN("", "."); + + /* Remove trailing slash */ + CLEAN("abc/", "abc"); + CLEAN("abc/def/", "abc/def"); + CLEAN("a/b/c/", "a/b/c"); + CLEAN("./", "."); + CLEAN("../", ".."); + CLEAN("../../", "../.."); + CLEAN("/abc/", "/abc"); + + /* Remove doubled slash */ + CLEAN("abc//def//ghi", "abc/def/ghi"); + CLEAN("//abc", "/abc"); + CLEAN("///abc", "/abc"); + CLEAN("//abc//", "/abc"); + CLEAN("abc//", "abc"); + + /* Remove . elements */ + CLEAN("abc/./def", "abc/def"); + CLEAN("/./abc/def", "/abc/def"); + CLEAN("abc/.", "abc"); + + /* Remove .. elements */ + CLEAN("abc/def/ghi/../jkl", "abc/def/jkl"); + CLEAN("abc/def/../ghi/../jkl", "abc/jkl"); + CLEAN("abc/def/..", "abc"); + CLEAN("abc/def/../..", "."); + CLEAN("/abc/def/../..", "/"); + CLEAN("abc/def/../../..", ".."); + CLEAN("/abc/def/../../..", "/"); + CLEAN("abc/def/../../../ghi/jkl/../../../mno", "../../mno"); + CLEAN("/../abc", "/abc"); + + /* Combinations */ + CLEAN("abc/./../def", "def"); + CLEAN("abc//./../def", "def"); + CLEAN("abc/../../././../def", "../../def"); + + /* Add test case with "hidden" dir */ + CLEAN("foo//bar/.fizz/buzz", "foo/bar/.fizz/buzz"); + CLEAN(".fizz/buzz", ".fizz/buzz"); + CLEAN(".fizz", ".fizz"); +} + +ATF_TC(xbps_path_rel); + +ATF_TC_HEAD(xbps_path_rel, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_path_rel"); +} + +ATF_TC_BODY(xbps_path_rel, tc) +{ + char buf[PATH_MAX]; + ssize_t len; + +#define REL(a, b, c) \ + len = xbps_path_rel(buf, sizeof buf, a, b); \ + ATF_CHECK_EQ(len, sizeof (c)-1); \ + ATF_CHECK_STREQ(buf, c) + + REL("/root/usr/bin/tar", "/root/usr/bin/gtar", "gtar"); + + REL("/root/usr/bin/java", "/root/usr/lib/jvm/jdk1.8.0_202/bin/java", + "../lib/jvm/jdk1.8.0_202/bin/java"); + + REL("/root/usr/..", "/root/usr/lib/..", "root/usr"); + REL("/root/usr/../bin", "/root/usr/lib/..", "usr"); + REL("/root/usr/../bin", "/root/usr/", "usr"); + + REL("/root/usr/bin/tar", "/root/usr/libexec/gtar", "../libexec/gtar"); + REL("/root/usr/bin//tar", "/root/usr/libexec/gtar", "../libexec/gtar"); + REL("/root/usr/bin//tar", "/root/usr/libexec//gtar", "../libexec/gtar"); + REL("/usr/bin/file", "/usr/bin/fileA", "fileA"); +} + +ATF_TC(xbps_path_join); + +ATF_TC_HEAD(xbps_path_join, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_path_join"); +} + +ATF_TC_BODY(xbps_path_join, tc) +{ + char buf[6]; + ssize_t len; + + len = xbps_path_join(buf, sizeof buf, "a", "b", "c", (char *)NULL); + ATF_CHECK_EQ(len, 5); + ATF_CHECK_STREQ(buf, "a/b/c"); + + len = xbps_path_join(buf, sizeof buf, "a/", "/b/", "/c", (char *)NULL); + ATF_CHECK_EQ(len, 5); + ATF_CHECK_STREQ(buf, "a/b/c"); + + len = xbps_path_join(buf, sizeof buf, "abc", "def", (char *)NULL); + ATF_CHECK_EQ(len, -1); + + len = xbps_path_join(buf, sizeof buf, "abcd", "ef", (char *)NULL); + ATF_CHECK_EQ(len, -1); + + len = xbps_path_join(buf, sizeof buf, "ab", "c", (char *)NULL); + ATF_CHECK_EQ(len, 4); + ATF_CHECK_STREQ(buf, "ab/c"); + + len = xbps_path_join(buf, sizeof buf, "ab/", "/c", (char *)NULL); + ATF_CHECK_EQ(len, 4); + ATF_CHECK_STREQ(buf, "ab/c"); + + len = xbps_path_join(buf, sizeof buf, "/ab/", "/c", (char *)NULL); + ATF_CHECK_EQ(len, sizeof ("/ab/c") - 1); + ATF_CHECK_STREQ(buf, "/ab/c"); + + len = xbps_path_join(buf, sizeof buf, "/a/", "/b/", (char *)NULL); + ATF_CHECK_EQ(len, sizeof ("/a/b/") - 1); + ATF_CHECK_STREQ(buf, "/a/b/"); + + len = xbps_path_join(buf, sizeof buf, "", "/a/", (char *)NULL); + ATF_CHECK_EQ(len, sizeof ("/a/") - 1); + ATF_CHECK_STREQ(buf, "/a/"); + + len = xbps_path_join(buf, sizeof buf, "a", "b/", (char *)NULL); + ATF_CHECK_EQ(len, sizeof ("a/b/") - 1); + ATF_CHECK_STREQ(buf, "a/b/"); + + len = xbps_path_join(buf, sizeof buf, "/", "a/", (char *)NULL); + ATF_CHECK_EQ(len, sizeof ("/a/") - 1); + ATF_CHECK_STREQ(buf, "/a/"); + + len = xbps_path_join(buf, sizeof buf, "/", "a", (char *)NULL); + ATF_CHECK_EQ(len, sizeof ("/a") - 1); + ATF_CHECK_STREQ(buf, "/a"); +} + +ATF_TC(xbps_path_append); + +ATF_TC_HEAD(xbps_path_append, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_path_append"); +} + +ATF_TC_BODY(xbps_path_append, tc) +{ + char buf[16]; + ssize_t len; + + /* empty prefix */ + xbps_strlcpy(buf, "fizz", sizeof buf); + len = xbps_path_append(buf, sizeof buf, ""); + ATF_CHECK_EQ(len, sizeof ("fizz") - 1); + ATF_CHECK_STREQ(buf, "fizz"); + + /* empty dst */ + buf[0] = '\0'; + len = xbps_path_append(buf, sizeof buf, "buzz"); + ATF_CHECK_EQ(len, sizeof ("buzz") - 1); + ATF_CHECK_STREQ(buf, "buzz"); + + /* add slash */ + xbps_strlcpy(buf, "fizz", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "buzz"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* already has slash in dst */ + xbps_strlcpy(buf, "fizz/", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "buzz"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* already has slash in suffix */ + xbps_strlcpy(buf, "fizz", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "/buzz"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* slash in dst and suffix */ + xbps_strlcpy(buf, "fizz/", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "/buzz"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + xbps_strlcpy(buf, "abcdefghijklmno", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "pqrstuvwxyz"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "abcdefghijklmn", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "opqrstuvwxyz"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "abcdefghijklm", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "nopqrstuvwxyz"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "abcdefghijklmno/", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "pqrstuvwxyz"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "abcdefghijklmn/", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "opqrstuvwxyz"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "abcdefghijklm/", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "nopqrstuvwxyz"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "abcdefghijklmno", sizeof buf); + len = xbps_path_append(buf, sizeof buf, ""); + ATF_CHECK_EQ(len, sizeof ("abcdefghijklmno") - 1); + ATF_CHECK_STREQ(buf, "abcdefghijklmno"); + + xbps_strlcpy(buf, "abcdefghijklmn/", sizeof buf); + len = xbps_path_append(buf, sizeof buf, ""); + ATF_CHECK_EQ(len, sizeof ("abcdefghijklmn/") - 1); + ATF_CHECK_STREQ(buf, "abcdefghijklmn/"); + + xbps_strlcpy(buf, "", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "abcdefghijklmno"); + ATF_CHECK_EQ(len, sizeof ("abcdefghijklmno") - 1); + ATF_CHECK_STREQ(buf, "abcdefghijklmno"); + + xbps_strlcpy(buf, "", sizeof buf); + len = xbps_path_append(buf, sizeof buf, "abcdefghijklmn/"); + ATF_CHECK_EQ(len, sizeof ("abcdefghijklmn/") - 1); + ATF_CHECK_STREQ(buf, "abcdefghijklmn/"); +} + +ATF_TC(xbps_path_prepend); + +ATF_TC_HEAD(xbps_path_prepend, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_path_prepend"); +} + +ATF_TC_BODY(xbps_path_prepend, tc) +{ + char buf[16]; + ssize_t len; + + /* empty prefix */ + xbps_strlcpy(buf, "buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, ""); + ATF_CHECK_EQ(len, sizeof ("buzz") - 1); + ATF_CHECK_STREQ(buf, "buzz"); + + /* empty dst */ + buf[0] = '\0'; + len = xbps_path_prepend(buf, sizeof buf, "buzz"); + ATF_CHECK_EQ(len, sizeof ("buzz") - 1); + ATF_CHECK_STREQ(buf, "buzz"); + + /* add slash */ + xbps_strlcpy(buf, "buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* already has slash in dst */ + xbps_strlcpy(buf, "/buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* already has slash in prefix */ + xbps_strlcpy(buf, "buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz/"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* slash in dst and prefix */ + xbps_strlcpy(buf, "/buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz/"); + ATF_CHECK_EQ(len, sizeof ("fizz/buzz") - 1); + ATF_CHECK_STREQ(buf, "fizz/buzz"); + + /* check truncation no slashes */ + xbps_strlcpy(buf, "bar/buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz/foo"); + ATF_CHECK_EQ(len, -1); + + /* check truncation slash in dst*/ + xbps_strlcpy(buf, "/bar/buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz/foo"); + ATF_CHECK_EQ(len, -1); + + /* check truncation slash in prefix */ + xbps_strlcpy(buf, "bar/buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz/foo/"); + ATF_CHECK_EQ(len, -1); + + /* check truncation slash in both */ + xbps_strlcpy(buf, "/bar/buzz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "fizz/foo/"); + ATF_CHECK_EQ(len, -1); + + /* check truncation */ + xbps_strlcpy(buf, "pqrstuvwxyz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklmno"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "/opqrstuvwxyz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklmn/"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "/nopqrstuvwxyz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklm/"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "opqrstuvwxyz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklmn"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "nopqrstuvwxyz", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklm"); + ATF_CHECK_EQ(len, -1); + + xbps_strlcpy(buf, "", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklmno"); + ATF_CHECK_EQ(len, sizeof ("abcdefghijklmno") - 1); + ATF_CHECK_STREQ(buf, "abcdefghijklmno"); + + xbps_strlcpy(buf, "", sizeof buf); + len = xbps_path_prepend(buf, sizeof buf, "abcdefghijklm/"); + ATF_CHECK_EQ(len, sizeof ("abcdefghijklm/") - 1); + ATF_CHECK_STREQ(buf, "abcdefghijklm/"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, xbps_path_clean); + ATF_TP_ADD_TC(tp, xbps_path_rel); + ATF_TP_ADD_TC(tp, xbps_path_join); + ATF_TP_ADD_TC(tp, xbps_path_append); + ATF_TP_ADD_TC(tp, xbps_path_prepend); + return atf_no_error(); +}