summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2015-06-23 00:12:25 +0000
committerRich Felker <dalias@aerifal.cx>2015-06-23 00:29:57 +0000
commita59341420fdedb288d9ff80e73609ae44e9cf258 (patch)
tree558bcbc501cbe1be079db3c8cc4aa7383200a703
parent153e952e1a688859d7095345b17e6c1df74a295c (diff)
downloadmusl-a59341420fdedb288d9ff80e73609ae44e9cf258.tar.gz
reimplement strverscmp to fix corner cases
this interface is non-standardized and is a GNU invention, and as such, our implementation should match the behavior of the GNU function. one peculiarity the old implementation got wrong was the handling of all-zero digit sequences: they are supposed to compare greater than digit sequences of which they are a proper prefix, as in 009 < 00. in addition, high bytes were treated with char signedness rather than as unsigned. this was wrong regardless of what the GNU function does since the resulting order relation varied by arch. the new strverscmp implementation makes explicit the cases where the order differs from what strcmp would produce, of which there are only two.
-rw-r--r--src/string/strverscmp.c57
1 files changed, 25 insertions, 32 deletions
diff --git a/src/string/strverscmp.c b/src/string/strverscmp.c
index 6f37cc68..4daf276d 100644
--- a/src/string/strverscmp.c
+++ b/src/string/strverscmp.c
@@ -2,40 +2,33 @@
#include <ctype.h>
#include <string.h>
-int strverscmp(const char *l, const char *r)
+int strverscmp(const char *l0, const char *r0)
{
- int haszero=1;
- while (*l==*r) {
- if (!*l) return 0;
+ const unsigned char *l = (const void *)l0;
+ const unsigned char *r = (const void *)r0;
+ size_t i, dp, j;
+ int z = 1;
- if (*l=='0') {
- if (haszero==1) {
- haszero=0;
- }
- } else if (isdigit(*l)) {
- if (haszero==1) {
- haszero=2;
- }
- } else {
- haszero=1;
- }
- l++; r++;
+ /* Find maximal matching prefix and track its maximal digit
+ * suffix and whether those digits are all zeros. */
+ for (dp=i=0; l[i]==r[i]; i++) {
+ int c = l[i];
+ if (!c) return 0;
+ if (!isdigit(c)) dp=i+1, z=1;
+ else if (c!='0') z=0;
}
- if (haszero==1 && (*l=='0' || *r=='0')) {
- haszero=0;
- }
- if ((isdigit(*l) && isdigit(*r) ) && haszero) {
- size_t lenl=0, lenr=0;
- while (isdigit(l[lenl]) ) lenl++;
- while (isdigit(r[lenr]) ) lenr++;
- if (lenl==lenr) {
- return (*l - *r);
- } else if (lenl>lenr) {
- return 1;
- } else {
- return -1;
- }
- } else {
- return (*l - *r);
+
+ if (l[dp]!='0' && r[dp]!='0') {
+ /* If we're not looking at a digit sequence that began
+ * with a zero, longest digit string is greater. */
+ for (j=i; isdigit(l[j]); j++)
+ if (!isdigit(r[j])) return 1;
+ if (isdigit(r[j])) return -1;
+ } else if (z && dp<i && (isdigit(l[i]) || isdigit(r[i]))) {
+ /* Otherwise, if common prefix of digit sequence is
+ * all zeros, digits order less than non-digits. */
+ return (unsigned char)(l[i]-'0') - (unsigned char)(r[i]-'0');
}
+
+ return l[i] - r[i];
}