[DTrace-devel] [PATCH v2] Add support for cleanpath() subroutine
eugene.loh at oracle.com
eugene.loh at oracle.com
Wed Dec 20 00:21:05 UTC 2023
From: Eugene Loh <eugene.loh at oracle.com>
Replace all "//" with "/".
Replace all "/./" with "/".
Replace all "/foo/.." with "/".
Notice that the results differ in some cases from what was
done in legacy DTrace:
input string current patch legacy DTrace
"/." "/" "/."
"a/." "a" "a/."
"./a" "a" "./a"
"../.." "../.." "."
"a/.." "." "a"
"a/../.." ".." "a"
"a/../b" "b" "/b"
In the first set, legacy DTrace has unnecessary trailing "." or "/."
or unnecessary leading "./". They would seem to be wrong.
In the next cases, the legacy results are clearly wrong, not
handling ".." correctly.
In the final case, a relative path is converted into an absolute
path, which is also clearly incorrect.
Signed-off-by: Eugene Loh <eugene.loh at oracle.com>
---
bpf/Build | 1 +
bpf/cleanpath.S | 478 ++++++++++++++++++++++++++
libdtrace/dt_cg.c | 9 +-
libdtrace/dt_impl.h | 9 +-
test/stress/fbtsafety/tst.cleanpath.d | 1 -
test/unittest/dif/cleanpath.d | 1 -
test/unittest/funcs/tst.cleanpath.d | 26 +-
test/unittest/funcs/tst.cleanpath.r | 30 +-
8 files changed, 547 insertions(+), 8 deletions(-)
create mode 100644 bpf/cleanpath.S
diff --git a/bpf/Build b/bpf/Build
index 917e8a0e..fa7d8b4f 100644
--- a/bpf/Build
+++ b/bpf/Build
@@ -23,6 +23,7 @@ bpf_dlib_SRCDEPS = $(objdir)/include/.dir.stamp
bpf_dlib_SOURCES = \
agg_lqbin.c agg_qbin.c \
basename.S \
+ cleanpath.S \
dirname.S \
get_agg.c \
get_bvar.c \
diff --git a/bpf/cleanpath.S b/bpf/cleanpath.S
new file mode 100644
index 00000000..3d537e2f
--- /dev/null
+++ b/bpf/cleanpath.S
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * Conceptually, the idea behind cleanpath() is relatively simple:
+ * simplify gratuitous "//", "/./" and "/foo/../" sequences in a path name.
+ *
+ * In practice, the algorithm is a little tricky, all the more since we
+ * want to implement this in BPF.
+ *
+ * Significantly, the BPF verifier really does not like conditional
+ * statements, since it has to verify every conceivable execution path,
+ * and the number of such paths can explode exponentially. So, we go to
+ * great lengths to replace conditional execution with nonconditional
+ * execution. For example,
+ * if (nup > 0)
+ * nup--;
+ * can be implemented as:
+ * tmp = (-nup) >> 63;
+ * nup -= tmp;
+ *
+ * Here is the high-level algorithm. Milestones are designated with
+ * "#" so that they can be found easily in the code. Comments like
+ * "execute if TMP==1" indicate which variable is set to 1 (or 0) to
+ * indicate that a code block should be executed (or not).
+ *
+ * // #1. handle empty string: cleanpath("") = ""
+ * if (strlen(src) == 0) {
+ * dst = "";
+ * return;
+ * }
+ *
+ * // #2. handle single-char string: cleanpath("x") = "x", even for '.' and '/'
+ * if (strlen(src) == 1)
+ * return;
+ *
+ * lastchar = src[strlen(src) - 1];
+ *
+ * // #3. regularize processing by prepending and appending a '/'
+ * dst = '/' + dst + '/';
+ *
+ * // #4. loop forward to handle "//" and "/./" strings
+ * seq = 0; // word whose bytes are the most recent chars we have seen
+ * for (to = from = 0; ; to++, from++) { // "from" advances faster than "to"
+ * char chr;
+ *
+ * chr = dst[from];
+ * dst[to] = chr;
+ * if (chr == '\0')
+ * break;
+ * seq = (seq << 8) | chr;
+ *
+ * if (recent two chars in seq are "//") {
+ * // #5. execute if TMP==1
+ * to--;
+ * seq >>= 8;
+ * }
+ *
+ * if (recent three chars in seq are "/./") {
+ * // #6. execute if TMP==1
+ * to -= 2;
+ * seq >>= 16;
+ * }
+ * }
+ *
+ * // #7. loop backward to handle "/../" strings
+ * nup = 0; // how many ".." are unresolved
+ * brk = ito; // index of last '/' we have seen
+ * seq = 0; // word whose bytes are the most recent chars we have seen
+ * for (to = from = brk - 1; from >= 0; to--, from--) { // "from" descends faster than "to"
+ * char chr;
+ *
+ * chr = dst[from];
+ * dst[to] = chr;
+ * seq = (seq << 8) | chr;
+ *
+ * if (recent four chars in seq are "/../") { // drop 3 chars and increment nup
+ * // #8. execute if TMP==1
+ * nup++;
+ * to += 3;
+ * seq >>= 24;
+ * } else if (chr == '/') { // completed a dir name that is not ".."
+ * // #9. execute if TMP==1
+ * if (nup > 0) { // we have to drop the dir name due to a ".."
+ * // #10. execute if TM4==1
+ * to = brk; // rewind to last brk index
+ * nup--; // decrement nup
+ * } else { // normal case, keep this dir name
+ * // #11. execute if TM2==1
+ * brk = ito; // note brk for future use
+ * }
+ * }
+ * }
+ *
+ * // #12. point to the first char in the output
+ * to++;
+ *
+ * // #13. for absolute paths, forget about nup: "/.." is equivalent to "/"
+ * if (src[0] == '/')
+ * goto Lout;
+ *
+ * // #14. for relative paths, advance past the initial, artificial '/'
+ * to++;
+ *
+ * // #15. for each nup, prepend a "../"
+ * while (nup > 0) {
+ * ito -= 3;
+ * dst[ITO + 0] = '.';
+ * dst[ITO + 1] = '.';
+ * dst[ITO + 2] = '/';
+ * nup--;
+ * }
+ *
+ * Lout:
+ *
+ * // #16. move the result string to the beginning of the dst buffer
+ * strcpy(dst, &dst[to]);
+ *
+ * // #17. check for empty string
+ * if (strlen(dst) < 1) {
+ * if (lastchar != '/')
+ * dst = ".";
+ * else
+ * dst = "./";
+ * return;
+ * }
+ *
+ * // #18. check for single-char string
+ * if (strlen(dst) < 2)
+ * return;
+ *
+ * // #19. strip off the trailing '/'
+ * if (lastchar != '/')
+ * dst[strlen(dst) - 1] = '\0';
+ */
+
+#define BPF_FUNC_probe_read 4
+#define BPF_FUNC_probe_read_str 45
+
+/*
+ * void dt_cleanpath(const dt_dctx_t *dctx, char *src, char *dst);
+ */
+ .text
+ .align 4
+ .global dt_cleanpath
+ .type dt_cleanpath, @function
+dt_cleanpath :
+
+#define DST %r9
+ /* Cache dst pointer. */
+ mov DST, %r3
+
+ /*
+ * Copy src to &dst[1]. Then we can directly dereference individual chars.
+ * The first byte, dst[0], is reserved for prepending a '/'.
+ */
+ mov %r3, %r2
+ lddw %r2, STRSZ
+ add %r2, 1
+ mov %r1, DST
+ add %r1, 1
+ call BPF_FUNC_probe_read_str
+ /* At this point, %r0 has strlen(src) + 1. */
+
+ /* #1. Handle empty string: cleanpath("") = "". */
+ jsgt %r0, 1, .Lnotempty
+ stb [DST+0], 0
+ mov %r0, 0
+ exit
+.Lnotempty:
+
+ /*
+ * #2. Handle single-char string: cleanpath("x") = "x", even for '.' and '/'.
+ * Load the first char (which we had put in dst[1]) and stuff it in dst[0].
+ */
+ ldxb %r1, [DST+1]
+ jgt %r0, 2, .Lnotsinglechar
+ stxb [DST+0], %r1
+ stb [DST+1], 0
+ mov %r0, 0
+ exit
+.Lnotsinglechar:
+
+#define FIRSTCHAR [%fp+-2]
+#define LASTCHAR [%fp+-1]
+ /* Save the first char. */
+ stxb FIRSTCHAR, %r1
+
+ /* Save the last char. */
+ mov %r8, %r0
+ add %r8, DST
+ ldxb %r8, [%r8+-1]
+ stxb LASTCHAR, %r8
+
+ /* #3. Prepend a '/'. */
+ stb [DST+0], '/'
+
+ /* #3. Append a '/'. */
+ add %r0, DST
+ stb [%r0+0], '/'
+ stb [%r0+1], 0
+
+#define TMP %r8 /* temp reg */
+#define ITO %r7 /* index to copy "to" */
+#define IFR %r6 /* index to copy "from" */
+#define SEQ %r5 /* sequence of most-recent chars */
+#define CHR %r4 /* char being copied */
+#define SIZ %r3 /* STRSZ */
+
+ /* #4. Loop forward through the string. */
+ mov ITO, 0
+ mov IFR, 0
+ mov SEQ, 0
+ lddw SIZ, STRSZ
+.Lforward:
+ jge IFR, SIZ, .Lret /* reassure the BPF verifier */
+
+ /* Load the char from the "from" index. */
+ mov TMP, IFR
+ add TMP, DST
+ ldxb CHR, [TMP + 0]
+
+ /* Store the char to the "to" index. */
+ mov TMP, ITO
+ jlt TMP, 0, .Lret /* reassure the BPF verifier */
+ jgt TMP, IFR, .Lret /* reassure the BPF verifier */
+ add TMP, DST
+ stxb [TMP + 0], CHR
+
+ /* If the char is '\0', break out of the loop. */
+ jeq CHR, 0, .Lforward_brk
+
+ /* Add this char to the sequence of chars. */
+ lsh SEQ, 8
+ or SEQ, CHR
+
+ /* #5. Compute TMP = 1 (if we should execute), else 0. */
+ mov TMP, (('/' << 8) | '/')
+ xor TMP, SEQ
+ and TMP, 0xffff
+ neg TMP
+ rsh TMP, 63
+ xor TMP, 1
+
+ /* Use this to back up one char if "//". */
+ sub ITO, TMP
+
+ /* Update SEQ accordingly. */
+ mul TMP, 8
+ rsh SEQ, TMP
+
+ /* #6. Compute TMP = 1 (if we should execute), else 0. */
+ mov TMP, (('/' << 16) | ('.' << 8) | '/')
+ xor TMP, SEQ
+ and TMP, 0xffffff
+ neg TMP
+ rsh TMP, 63
+ xor TMP, 1
+
+ /* Use this to back up two chars if "/./". */
+ mul TMP, 2
+ sub ITO, TMP
+
+ /* Update SEQ accordingly. */
+ mul TMP, 8
+ rsh SEQ, TMP
+
+ /* Advance the indices and iterate. */
+ add ITO, 1
+ add IFR, 1
+ ja .Lforward
+#undef SIZ
+
+.Lforward_brk:
+
+#define TM2 %r3 /* another temp reg */
+#define TM3 %r2 /* another temp reg */
+#define BRK %r1 /* index of last directory-level break '/' */
+#define NUP %r0 /* num of parent directories to go up due to "../" */
+
+ /* #7. Handle double-dot "/../", looping backward. */
+ mov NUP, 0
+ mov SEQ, 0
+ mov BRK, ITO
+ jslt BRK, 1, .Lret /* reassure the BPF verifier */
+ lddw TMP, STRSZ
+ jge BRK, TMP, .Lret /* reassure the BPF verifier */
+ mov IFR, BRK
+ sub IFR, 1
+ mov ITO, IFR
+.Lbackward:
+ /* Load the char from the "from" index. */
+ mov TMP, IFR
+ add TMP, DST
+ ldxb CHR, [TMP + 0]
+
+ /* Store the char to the "to" index. */
+ mov TMP, ITO
+ lddw TM2, STRSZ
+ jge TMP, TM2, .Lret /* reassure the BPF verifier */
+ jlt TMP, IFR, .Lret /* reassure the BPF verifier */
+ add TMP, DST
+ stxb [TMP + 0], CHR
+
+ /* Add this char to the sequence of chars. */
+ lsh SEQ, 8
+ or SEQ, CHR
+
+ /* #8. Compute TMP = 1 (if we should execute), else 0. */
+ mov TMP, ('/' | ('.' << 8) | ('.' << 16) | ('/' << 24))
+ xor TMP, SEQ
+ lsh TMP, 32
+ rsh TMP, 32
+ neg TMP
+ rsh TMP, 63
+ xor TMP, 1
+
+ /* Use this to increase NUP if "/../". */
+ add NUP, TMP
+
+ /* Use this to back up three chars if "/../". */
+ mov TM2, TMP
+ mul TM2, 3
+ add ITO, TM2
+
+ /* Update SEQ accordingly. */
+ mul TM2, 8
+ rsh SEQ, TM2
+
+ /* Now switch to the not-"/.../" case. */
+ xor TMP, 1
+
+/* Rename CHR's reg TM4; TM4 is yet another temp reg and starts with CHR's value. */
+#undef CHR
+#define TM4 %r4
+
+ /* If the last char is "/", TM4 = 1. Else, TM4 = 0. */
+ xor TM4, '/'
+ neg TM4
+ rsh TM4, 63
+ xor TM4, 1
+
+ /* #9. Compute TMP = 1 (if we should execute), else 0. */
+ mul TMP, TM4
+
+ /* If NUP > 0, TM2 = 1. Else, TM2 = 0. */
+ mov TM2, NUP
+ neg TM2
+ rsh TM2, 63
+
+ /* #10. Compute TM4 = 1 (if we should execute), else 0. */
+ mov TM4, TMP
+ mul TM4, TM2
+
+ /* If TM4 is 1, then ITO = BRK (revert to last BRK index). */
+ mov TM3, BRK
+ sub TM3, ITO
+ mul TM3, TM4
+ add ITO, TM3
+
+ /* If TM4 is 1, then NUP--. */
+ sub NUP, TM4
+
+ /* #11. Compute TM2 = 1 (if we should execute), else 0. */
+ xor TM2, 1
+ mul TM2, TMP
+
+ /* If TM2 is 1, then BRK = ITO. */
+ mov TM4, ITO
+ sub TM4, BRK
+ mul TM4, TM2
+ add BRK, TM4
+
+ /* Advance the indices and iterate. */
+ sub ITO, 1
+ sub IFR, 1
+ jsge IFR, 0, .Lbackward
+#undef BRK
+#undef TM4
+#undef TM2
+#undef SEQ
+#undef TM3
+#undef IFR
+
+ /*
+ * At this point, we've finished the backward loop.
+ */
+
+ /* #12. Increment ITO to point to the beginning of the output. */
+ add ITO, 1
+
+ /* #13. If we have an absolute path (src[0] == '/'), go to Lout. */
+ ldxb TMP, FIRSTCHAR
+ jeq TMP, '/', .Lout
+#undef FIRSTCHAR
+
+ /* #14. Otherwise, advance past the initial, spurious '/'. */
+ add ITO, 1
+
+ /* #15. Add a "../" prefix NUP times. */
+ /*
+ * FIXME: The BPF verifier needs some assurance that this loop
+ * is not endless. We arbitrarily assume NUP is at most 15,
+ * equivalent to some clean path of the form
+ * "../../../../../../../../../../../../../../../foo/bar".
+ */
+ and NUP, 15
+
+ lddw TMP, STRSZ
+ jgt ITO, TMP, .Lret /* reassure the BPF verifier */
+.Lprefix:
+ jsle NUP, 0, .Lout
+
+ sub ITO, 3
+ jslt ITO, 0, .Lret /* reassure the BPF verifier */
+
+ mov TMP, ITO
+ add TMP, DST
+ stb [TMP+0], '.'
+ stb [TMP+1], '.'
+ stb [TMP+2], '/'
+
+ sub NUP, 1
+ ja .Lprefix
+#undef NUP
+#undef TMP
+
+.Lout:
+ jslt ITO, 0, .Lret /* reassure the BPF verifier */
+ lddw %r1, STRSZ
+ jgt ITO, %r1, .Lret /* reassure the BPF verifier */
+
+ /* #16. Copy from index "ito" to the beginning of the DST buffer. */
+ /*
+ * FIXME: Is this okay? We are copying a string in the DST
+ * array from offset ITO down to offset 0. The input and output
+ * strings might (indeed probably will) overlap. Can we use
+ * the BPF helper function in this way?
+ */
+ mov %r1, DST
+ lddw %r2, STRSZ
+ add %r2, 1
+ sub %r2, ITO
+ mov %r3, DST
+ add %r3, ITO
+ call BPF_FUNC_probe_read_str
+ /* At this point, %r0 has strlen(dst) + 1. */
+#undef ITO
+
+ /* #17. If the string is empty, then make it ".\0" or "./\0" and return. */
+ jsge %r0, 2, .Lnotempty2
+ ldxb %r1, LASTCHAR
+ jeq %r1, '/', .Laddslash
+ sth [DST+0], '.' /* little-endian uses "\0.", or just '.' */
+ ja .Lret
+.Laddslash:
+ sth [DST+0], (('/' << 8) | '.') /* little-endian uses "/." */
+ stb [DST+2], 0
+ ja .Lret
+.Lnotempty2:
+
+ /* #18. If the string is a single char (plus NUL char), then return. */
+ jle %r0, 2, .Lret
+
+ /* #19. Strip off the trailing '/'. */
+ ldxb %r1, LASTCHAR
+ jeq %r1, '/', .Lret
+ add %r0, DST
+ stb [%r0+-2], 0
+#undef DST
+#undef LASTCHAR
+
+.Lret:
+ mov %r0, 0
+ exit
+ .size dt_cleanpath, .-dt_cleanpath
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 503e5c9c..1630750f 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -4991,6 +4991,13 @@ dt_cg_subr_basename(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
DT_IGNOR, 0);
}
+static void
+dt_cg_subr_cleanpath(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
+{
+ dt_cg_subr_arg_to_tstring(dnp, dlp, drp, "dt_cleanpath", DT_IGNOR, 0,
+ DT_IGNOR, 0);
+}
+
static void
dt_cg_subr_dirname(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
{
@@ -6391,7 +6398,7 @@ static dt_cg_subr_f *_dt_cg_subr[DIF_SUBR_MAX + 1] = {
[DIF_SUBR_LLTOSTR] = &dt_cg_subr_lltostr,
[DIF_SUBR_BASENAME] = &dt_cg_subr_basename,
[DIF_SUBR_DIRNAME] = &dt_cg_subr_dirname,
- [DIF_SUBR_CLEANPATH] = NULL,
+ [DIF_SUBR_CLEANPATH] = &dt_cg_subr_cleanpath,
[DIF_SUBR_STRCHR] = &dt_cg_subr_strchr,
[DIF_SUBR_STRRCHR] = &dt_cg_subr_strrchr,
[DIF_SUBR_STRSTR] = &dt_cg_subr_strstr,
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index 17b13d6a..227f0e07 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -226,10 +226,17 @@ typedef struct dt_ahash {
*
* Each tstring needs to be large enough to hold the largest possible string
* and accomodate the largest known need for tstring space in subroutines.
+ *
+ * For example:
+ *
+ * - inet_ntoa6() stores its output and 2 copies of the input (40 + 2 * 16 = 72)
+ *
+ * - cleanpath() holds a prepended '/' char, a string, an appended '/' char,
+ * and a terminating NUL char, or STRSZ + 3 chars altogether
*/
#define DT_TSTRING_SLOTS 4
#define DT_TSTRING_SIZE(dtp) \
- MAX(P2ROUNDUP((dtp)->dt_options[DTRACEOPT_STRSIZE] + 1, 8), \
+ MAX(P2ROUNDUP((dtp)->dt_options[DTRACEOPT_STRSIZE] + 3, 8), \
72)
typedef struct dt_tstring {
diff --git a/test/stress/fbtsafety/tst.cleanpath.d b/test/stress/fbtsafety/tst.cleanpath.d
index d3b934dc..1723a4a7 100644
--- a/test/stress/fbtsafety/tst.cleanpath.d
+++ b/test/stress/fbtsafety/tst.cleanpath.d
@@ -4,7 +4,6 @@
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
-/* @@xfail: dtv2 */
#pragma D option bufsize=1000
#pragma D option bufpolicy=ring
diff --git a/test/unittest/dif/cleanpath.d b/test/unittest/dif/cleanpath.d
index 0e6c39f9..3fc3d4c5 100644
--- a/test/unittest/dif/cleanpath.d
+++ b/test/unittest/dif/cleanpath.d
@@ -1,4 +1,3 @@
-/* @@xfail: dtv2 */
BEGIN
{
exit(cleanpath("/foo/bar/.././../sys/foo") == "/sys/foo" ? 0 : 1);
diff --git a/test/unittest/funcs/tst.cleanpath.d b/test/unittest/funcs/tst.cleanpath.d
index bdcde04b..0695148c 100644
--- a/test/unittest/funcs/tst.cleanpath.d
+++ b/test/unittest/funcs/tst.cleanpath.d
@@ -4,7 +4,6 @@
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
-/* @@xfail: dtv2 */
#pragma D option quiet
@@ -32,6 +31,31 @@ BEGIN
path[i++] = "/////";
path[i++] = "";
+ path[i++] = "a/.";
+ path[i++] = "a/./.";
+ path[i++] = "./a";
+ path[i++] = "././a";
+ path[i++] = "..";
+ path[i++] = "../..";
+ path[i++] = "../../..";
+ path[i++] = "../../../..";
+ path[i++] = "a/..";
+ path[i++] = "abc";
+ path[i++] = "a/";
+ path[i++] = "ab/";
+ path[i++] = "/abc";
+ path[i++] = "z/a/../b";
+ path[i++] = "/z/a/../b";
+ path[i++] = "z/a/b/../../././c///d/e";
+ path[i++] = "/z/a/b/../../././c///d/e";
+ path[i++] = "/a/../b";
+ path[i++] = "/a/..";
+ path[i++] = "/a/../..";
+ path[i++] = "/a/b/../../././c///d/e";
+ path[i++] = "a/../b";
+ path[i++] = "a/../..";
+ path[i++] = "a/b/../../././c///d/e";
+
end = i;
i = 0;
}
diff --git a/test/unittest/funcs/tst.cleanpath.r b/test/unittest/funcs/tst.cleanpath.r
index b8bdc099..76f8f584 100644
--- a/test/unittest/funcs/tst.cleanpath.r
+++ b/test/unittest/funcs/tst.cleanpath.r
@@ -2,7 +2,7 @@ cleanpath("/foo/bar/baz") = "/foo/bar/baz"
cleanpath("/foo/bar///baz/") = "/foo/bar/baz/"
cleanpath("/foo/bar/baz/") = "/foo/bar/baz/"
cleanpath("/foo/bar/baz//") = "/foo/bar/baz/"
-cleanpath("/foo/bar/baz/.") = "/foo/bar/baz/."
+cleanpath("/foo/bar/baz/.") = "/foo/bar/baz"
cleanpath("/foo/bar/baz/./") = "/foo/bar/baz/"
cleanpath("/foo/bar/../../baz/.//") = "/baz/"
cleanpath("foo/bar/./././././baz/") = "foo/bar/baz/"
@@ -12,11 +12,35 @@ cleanpath("/./") = "/"
cleanpath("/foo/bar/baz/../../bop/bang/../../bar/baz/") = "/foo/bar/baz/"
cleanpath("./") = "./"
cleanpath("//") = "/"
-cleanpath("/.") = "/."
+cleanpath("/.") = "/"
cleanpath("/./") = "/"
-cleanpath("/./.") = "/."
+cleanpath("/./.") = "/"
cleanpath("/.//") = "/"
cleanpath(".") = "."
cleanpath("/////") = "/"
cleanpath("") = ""
+cleanpath("a/.") = "a"
+cleanpath("a/./.") = "a"
+cleanpath("./a") = "a"
+cleanpath("././a") = "a"
+cleanpath("..") = ".."
+cleanpath("../..") = "../.."
+cleanpath("../../..") = "../../.."
+cleanpath("../../../..") = "../../../.."
+cleanpath("a/..") = "."
+cleanpath("abc") = "abc"
+cleanpath("a/") = "a/"
+cleanpath("ab/") = "ab/"
+cleanpath("/abc") = "/abc"
+cleanpath("z/a/../b") = "z/b"
+cleanpath("/z/a/../b") = "/z/b"
+cleanpath("z/a/b/../../././c///d/e") = "z/c/d/e"
+cleanpath("/z/a/b/../../././c///d/e") = "/z/c/d/e"
+cleanpath("/a/../b") = "/b"
+cleanpath("/a/..") = "/"
+cleanpath("/a/../..") = "/"
+cleanpath("/a/b/../../././c///d/e") = "/c/d/e"
+cleanpath("a/../b") = "b"
+cleanpath("a/../..") = ".."
+cleanpath("a/b/../../././c///d/e") = "c/d/e"
--
2.18.4
More information about the DTrace-devel
mailing list