[DTrace-devel] [PATCH v4] Add support for cleanpath() subroutine

Kris Van Hees kris.van.hees at oracle.com
Fri Jan 26 00:31:06 UTC 2024


On Thu, Jan 25, 2024 at 07:17:30PM -0500, eugene.loh--- via DTrace-devel wrote:
> From: Eugene Loh <eugene.loh at oracle.com>
> 
> Common simplifications include:
> - replace all "//" with "/"
> - replace all "/./" with "/"
> - replace all "/foo/.." with "/"
> but precise semantics can get tricky in certain cases.
> 
> 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>

Reviewed-by: Kris Van Hees <kris.van.hees at oracle.com>

> ---
>  bpf/Build                             |   1 +
>  bpf/cleanpath.S                       | 463 ++++++++++++++++++++++++++
>  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   |  46 ++-
>  8 files changed, 540 insertions(+), 16 deletions(-)
>  create mode 100644 bpf/cleanpath.S
> 
> diff --git a/bpf/Build b/bpf/Build
> index a165cbd3..20202d90 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..5035ef0a
> --- /dev/null
> +++ b/bpf/cleanpath.S
> @@ -0,0 +1,463 @@
> +// 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) {
> + *      	dst = ".";
> + *      	return;
> + *      }
> + *
> + *      // #18.  check for single-char string
> + *      if (strlen(dst) < 2)
> + *      	return;
> + *
> + *      // #19.  strip off the trailing '/'
> + *      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+-1]
> +	/* Save the first char. */
> +	stxb	FIRSTCHAR, %r1
> +
> +	/* Save the last char. */
> +	mov	%r8, %r0
> +	add	%r8, DST
> +	ldxb	%r8, [%r8+-1]
> +
> +	/* #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, make it ".\0" and return. */
> +	jsge	%r0, 2, .Lnotempty2
> +	sth	[DST+0], '.' /* little-endian uses "\0.", or just '.' */
> +	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 '/'. */
> +	add	%r0, DST
> +	stb	[%r0+-2], 0
> +#undef DST
> +
> +.Lret:
> +	mov	%r0, 0
> +	exit
> +	.size	dt_cleanpath, .-dt_cleanpath
> diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
> index 9221e0f6..d312e319 100644
> --- a/libdtrace/dt_cg.c
> +++ b/libdtrace/dt_cg.c
> @@ -5283,6 +5283,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", 0, DT_IGNOR, 0,
> +				  DT_IGNOR, 0);
> +}
> +
>  static void
>  dt_cg_subr_dirname(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
>  {
> @@ -6719,7 +6726,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 c38d32d5..9912ad65 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..ef603b74 100644
> --- a/test/unittest/funcs/tst.cleanpath.r
> +++ b/test/unittest/funcs/tst.cleanpath.r
> @@ -1,22 +1,46 @@
>  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/"
> +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"
>  cleanpath("/foo/bar/baz/../../../../../../") = "/"
>  cleanpath("/../../../../../../") = "/"
>  cleanpath("/./") = "/"
> -cleanpath("/foo/bar/baz/../../bop/bang/../../bar/baz/") = "/foo/bar/baz/"
> -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
> 
> 
> _______________________________________________
> DTrace-devel mailing list
> DTrace-devel at oss.oracle.com
> https://oss.oracle.com/mailman/listinfo/dtrace-devel



More information about the DTrace-devel mailing list