From be0a98abf6aa0c4c3fc17673e7d292eb4b101853 Mon Sep 17 00:00:00 2001 From: kre Date: Wed, 10 May 2017 06:18:43 +0000 Subject: [PATCH] I noticed! POSIX requires that the output of the "set" command (with no args -- it gives a list of variables, and their values) be sorted according to the collating sequence defined by the current locale. Now I'm not aware of any locale where the collating sequence order of ascii letters, digits, and '_' are any different than they are in the C locale (and those are the only characters that can occur in variable names - unless there is perhaps a locale that defines "dictionary" order as the sort order) but never mind, that isn't the bug... What "collating sequence order" does mean however, if not "collating sequence order, except when we happen to have two variable names, where one name is a prefix of the other (say X and XY) and the first character of the 'Y' part of the longer name happens to be a digit..." "set" is not a frequently used command (particularly in scripts where it matters - that is, the no args form, nothing here alters anything about any use of set with args) and is already a bit slow (sluggish...) because of the sort requirement, so let's make it fractionally even slower, but correct. --- bin/sh/var.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/bin/sh/var.c b/bin/sh/var.c index 29d342e2b3d8..abd42bbd5f06 100644 --- a/bin/sh/var.c +++ b/bin/sh/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.51 2017/05/03 00:39:40 kre Exp $ */ +/* $NetBSD: var.c,v 1.52 2017/05/10 06:18:43 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; #else -__RCSID("$NetBSD: var.c,v 1.51 2017/05/03 00:39:40 kre Exp $"); +__RCSID("$NetBSD: var.c,v 1.52 2017/05/10 06:18:43 kre Exp $"); #endif #endif /* not lint */ @@ -526,9 +526,34 @@ sort_var(const void *v_v1, const void *v_v2) { const struct var * const *v1 = v_v1; const struct var * const *v2 = v_v2; + char *t1 = (*v1)->text, *t2 = (*v2)->text; - /* XXX Will anyone notice we include the '=' of the shorter name? */ - return strcoll((*v1)->text, (*v2)->text); + if (*t1 == *t2) { + char *p, *s; + + STARTSTACKSTR(p); + + /* + * note: if lengths are equal, strings must be different + * so we don't care which string we pick for the \0 in + * that case. + */ + if ((strchr(t1, '=') - t1) <= (strchr(t2, '=') - t2)) { + s = t1; + t1 = p; + } else { + s = t2; + t2 = p; + } + + while (*s && *s != '=') { + STPUTC(*s, p); + s++; + } + STPUTC('\0', p); + } + + return strcoll(t1, t2); } /*