The function qsort_s() was introduced with C11 Annex K; it provides the
ability to pass a context pointer to the comparison function, supports
the convention of using a NULL pointer for an empty array and performs a
few safety checks.

Add an implementation based on compat/qsort.c for platforms that lack a
native standards-compliant qsort_s() (i.e. basically everyone).  It
doesn't perform the full range of possible checks: It uses size_t
instead of rsize_t and doesn't check nmemb and size against RSIZE_MAX
because we probably don't have the restricted size type defined.  For
the same reason it returns int instead of errno_t.

Signed-off-by: Rene Scharfe <l....@web.de>
---
 Makefile          | 10 ++++++++
 compat/qsort_s.c  | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h |  6 +++++
 3 files changed, 85 insertions(+)
 create mode 100644 compat/qsort_s.c

diff --git a/Makefile b/Makefile
index f53fcc90d..2245fd95d 100644
--- a/Makefile
+++ b/Makefile
@@ -279,6 +279,9 @@ all::
 # is a simplified version of the merge sort used in glibc. This is
 # recommended if Git triggers O(n^2) behavior in your platform's qsort().
 #
+# Define HAVE_QSORT_S if your platform provides a qsort_s() that's
+# compatible with the one described in C11 Annex K.
+#
 # Define UNRELIABLE_FSTAT if your system's fstat does not return the same
 # information on a not yet closed file that lstat would return for the same
 # file after it was closed.
@@ -1423,6 +1426,13 @@ ifdef INTERNAL_QSORT
        COMPAT_CFLAGS += -DINTERNAL_QSORT
        COMPAT_OBJS += compat/qsort.o
 endif
+
+ifdef HAVE_QSORT_S
+       COMPAT_CFLAGS += -DHAVE_QSORT_S
+else
+       COMPAT_OBJS += compat/qsort_s.o
+endif
+
 ifdef RUNTIME_PREFIX
        COMPAT_CFLAGS += -DRUNTIME_PREFIX
 endif
diff --git a/compat/qsort_s.c b/compat/qsort_s.c
new file mode 100644
index 000000000..52d1f0a73
--- /dev/null
+++ b/compat/qsort_s.c
@@ -0,0 +1,69 @@
+#include "../git-compat-util.h"
+
+/*
+ * A merge sort implementation, simplified from the qsort implementation
+ * by Mike Haertel, which is a part of the GNU C Library.
+ * Added context pointer, safety checks and return value.
+ */
+
+static void msort_with_tmp(void *b, size_t n, size_t s,
+                          int (*cmp)(const void *, const void *, void *),
+                          char *t, void *ctx)
+{
+       char *tmp;
+       char *b1, *b2;
+       size_t n1, n2;
+
+       if (n <= 1)
+               return;
+
+       n1 = n / 2;
+       n2 = n - n1;
+       b1 = b;
+       b2 = (char *)b + (n1 * s);
+
+       msort_with_tmp(b1, n1, s, cmp, t, ctx);
+       msort_with_tmp(b2, n2, s, cmp, t, ctx);
+
+       tmp = t;
+
+       while (n1 > 0 && n2 > 0) {
+               if (cmp(b1, b2, ctx) <= 0) {
+                       memcpy(tmp, b1, s);
+                       tmp += s;
+                       b1 += s;
+                       --n1;
+               } else {
+                       memcpy(tmp, b2, s);
+                       tmp += s;
+                       b2 += s;
+                       --n2;
+               }
+       }
+       if (n1 > 0)
+               memcpy(tmp, b1, n1 * s);
+       memcpy(b, t, (n - n2) * s);
+}
+
+int git_qsort_s(void *b, size_t n, size_t s,
+               int (*cmp)(const void *, const void *, void *), void *ctx)
+{
+       const size_t size = st_mult(n, s);
+       char buf[1024];
+
+       if (!n)
+               return 0;
+       if (!b || !cmp)
+               return -1;
+
+       if (size < sizeof(buf)) {
+               /* The temporary array fits on the small on-stack buffer. */
+               msort_with_tmp(b, n, s, cmp, buf, ctx);
+       } else {
+               /* It's somewhat large, so malloc it.  */
+               char *tmp = xmalloc(size);
+               msort_with_tmp(b, n, s, cmp, tmp, ctx);
+               free(tmp);
+       }
+       return 0;
+}
diff --git a/git-compat-util.h b/git-compat-util.h
index 87237b092..d25f0bd4c 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -988,6 +988,12 @@ static inline void sane_qsort(void *base, size_t nmemb, 
size_t size,
                qsort(base, nmemb, size, compar);
 }
 
+#ifndef HAVE_QSORT_S
+int git_qsort_s(void *base, size_t nmemb, size_t size,
+               int (*compar)(const void *, const void *, void *), void *ctx);
+#define qsort_s git_qsort_s
+#endif
+
 #ifndef REG_STARTEND
 #error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd"
 #endif
-- 
2.11.0

Reply via email to