utils: add osmo_str_to_int() and osmo_str_to_int64()

Properly converting a string to an integer while validating against all
possible errors is not trivial. It is a recurring theme in code review,
and there are places in osmo code that do it wrong.
End this by providing a simple API, if for nothing else then as an
example of how to use strol() / strtoul() / strtoll() / strtoull()
in an airtight way.

A subsequent patch, adding stat items to the CTRL interface, uses this
to properly validate indexes in CTRL variables and convert them to int.

Related: SYS#5542
Change-Id: I4dac826aab00bc1780a5258b6b55d34ce7d50c60
diff --git a/src/utils.c b/src/utils.c
index 721c34a..2b21dcc 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1405,4 +1405,98 @@
 	OSMO_NAME_C_IMPL(ctx, 16, "ERROR", osmo_int_to_float_str_buf, val, precision)
 }
 
+/*! Convert a string of a number to int64_t, including all common strtoll() validity checks.
+ * It's not so trivial to call strtoll() and properly verify that the input string was indeed a valid number string.
+ * \param[out] result  Buffer for the resulting integer number, or NULL if the caller is only interested in the
+ *                     validation result (returned rc).
+ * \param[in] str  The string to convert.
+ * \param[in] base  The integer base, i.e. 10 for decimal numbers or 16 for hexadecimal, as in strtoll().
+ * \param[in] min_val  The smallest valid number expected in the string.
+ * \param[in] max_val  The largest valid number expected in the string.
+ * \return 0 on success, -EOVERFLOW if the number in the string exceeds int64_t, -ENOTSUPP if the base is not supported,
+ * -ERANGE if the converted number exceeds the range [min_val..max_val] but is still within int64_t range, -E2BIG if
+ * surplus characters follow after the number, -EINVAL if the string does not contain a number. In case of -ERANGE and
+ * -E2BIG, the converted number is still accurately returned in result. In case of -EOVERFLOW, the returned value is
+ * clamped to INT64_MIN..INT64_MAX.
+ */
+int osmo_str_to_int64(int64_t *result, const char *str, int base, int64_t min_val, int64_t max_val)
+{
+	long long int val;
+	char *endptr;
+	if (result)
+		*result = 0;
+	if (!str || !*str)
+		return -EINVAL;
+	errno = 0;
+	val = strtoll(str, &endptr, base);
+	/* In case the number string exceeds long long int range, strtoll() clamps the returned value to LLONG_MIN or
+	 * LLONG_MAX. Make sure of the same here with respect to int64_t. */
+	if (val < INT64_MIN) {
+		if (result)
+			*result = INT64_MIN;
+		return -ERANGE;
+	}
+	if (val > INT64_MAX) {
+		if (result)
+			*result = INT64_MAX;
+		return -ERANGE;
+	}
+	if (result)
+		*result = (int64_t)val;
+	switch (errno) {
+	case 0:
+		break;
+	case ERANGE:
+		return -EOVERFLOW;
+	default:
+	case EINVAL:
+		return -ENOTSUP;
+	}
+	if (!endptr || *endptr) {
+		/* No chars were converted */
+		if (endptr == str)
+			return -EINVAL;
+		/* Or there are surplus chars after the converted number */
+		return -E2BIG;
+	}
+	if (val < min_val || val > max_val)
+		return -ERANGE;
+	return 0;
+}
+
+/*! Convert a string of a number to int, including all common strtoll() validity checks.
+ * Same as osmo_str_to_int64() but using the plain int data type.
+ * \param[out] result  Buffer for the resulting integer number, or NULL if the caller is only interested in the
+ *                     validation result (returned rc).
+ * \param[in] str  The string to convert.
+ * \param[in] base  The integer base, i.e. 10 for decimal numbers or 16 for hexadecimal, as in strtoll().
+ * \param[in] min_val  The smallest valid number expected in the string.
+ * \param[in] max_val  The largest valid number expected in the string.
+ * \return 0 on success, -EOVERFLOW if the number in the string exceeds int range, -ENOTSUPP if the base is not supported,
+ * -ERANGE if the converted number exceeds the range [min_val..max_val] but is still within int range, -E2BIG if
+ * surplus characters follow after the number, -EINVAL if the string does not contain a number. In case of -ERANGE and
+ * -E2BIG, the converted number is still accurately returned in result. In case of -EOVERFLOW, the returned value is
+ * clamped to INT_MIN..INT_MAX.
+ */
+int osmo_str_to_int(int *result, const char *str, int base, int min_val, int max_val)
+{
+	int64_t val;
+	int rc = osmo_str_to_int64(&val, str, base, min_val, max_val);
+	/* In case the number string exceeds long long int range, strtoll() clamps the returned value to LLONG_MIN or
+	 * LLONG_MAX. Make sure of the same here with respect to int. */
+	if (val < INT_MIN) {
+		if (result)
+			*result = INT_MIN;
+		return -EOVERFLOW;
+	}
+	if (val > INT_MAX) {
+		if (result)
+			*result = INT_MAX;
+		return -EOVERFLOW;
+	}
+	if (result)
+		*result = (int)val;
+	return rc;
+}
+
 /*! @} */