[PATCH 1/2] libbb: add support for tallying with files

Hemmo Nieminen hemmo.nieminen at iki.fi
Wed Apr 14 11:18:59 UTC 2021


From: Hemmo Nieminen <hemmo.nieminen at kone.com>

Signed-off-by: Hemmo Nieminen <hemmo.nieminen at kone.com>
---
 include/libbb.h  |   5 +
 libbb/Config.src |   6 +
 libbb/Kbuild.src |   1 +
 libbb/bb_tally.c | 471 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 483 insertions(+)
 create mode 100644 libbb/bb_tally.c

diff --git a/include/libbb.h b/include/libbb.h
index ece03e7d8..6880480e0 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -2565,6 +2565,11 @@ void bbunit_settestfailed(void);
 		} \
 	} while (0)
 
+#if ENABLE_FEATURE_TALLY
+int FAST_FUNC bb_tally_check(char const * const path, char const * const key);
+int FAST_FUNC bb_tally_add(char const * const path, char const * const key);
+int FAST_FUNC bb_tally_reset(char const * const path, char const * const key);
+#endif
 
 POP_SAVED_FUNCTION_VISIBILITY
 
diff --git a/libbb/Config.src b/libbb/Config.src
index f97de8ef7..23d821916 100644
--- a/libbb/Config.src
+++ b/libbb/Config.src
@@ -395,3 +395,9 @@ config FEATURE_HWIB
 	default y
 	help
 	Support for printing infiniband addresses in network applets.
+
+config FEATURE_TALLY
+	bool "Support tallying with files"
+	default n
+	help
+	Add support to libbb for tallying e.g. events via files.
diff --git a/libbb/Kbuild.src b/libbb/Kbuild.src
index 676300801..35448f805 100644
--- a/libbb/Kbuild.src
+++ b/libbb/Kbuild.src
@@ -153,6 +153,7 @@ lib-$(CONFIG_SULOGIN) += pw_encrypt.o correct_password.o
 lib-$(CONFIG_VLOCK) += pw_encrypt.o correct_password.o
 lib-$(CONFIG_SU) += pw_encrypt.o correct_password.o
 lib-$(CONFIG_LOGIN) += pw_encrypt.o correct_password.o
+lib-$(CONFIG_FEATURE_TALLY) += bb_tally.o
 lib-$(CONFIG_FEATURE_HTTPD_AUTH_MD5) += pw_encrypt.o
 lib-$(CONFIG_FEATURE_FTP_AUTHENTICATION) += pw_encrypt.o
 
diff --git a/libbb/bb_tally.c b/libbb/bb_tally.c
new file mode 100644
index 000000000..01901dd8d
--- /dev/null
+++ b/libbb/bb_tally.c
@@ -0,0 +1,471 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2021 Hemmo Nieminen
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+#include <errno.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <unistd.h>
+
+#include "libbb.h"
+
+#define KEYLEN_MAX 128
+
+static long
+digits(unsigned long val)
+{
+	long rv = 0;
+
+	if (!val) {
+		return 1;
+	}
+
+	while (val > 0) {
+		val /= 10;
+		++rv;
+	}
+
+	return rv;
+}
+
+static char *
+prepare_pattern(char const * key)
+{
+	char * buf;
+	ssize_t keylen = strlen(key),
+	        buflen = keylen + 7;
+
+	if (keylen > KEYLEN_MAX) {
+		return NULL;
+	}
+
+	buf = malloc(buflen);
+
+	if (!buf) {
+		return NULL;
+	}
+
+	if (snprintf(buf, buflen, "%s=%%lu\\n", key) != buflen - 1) {
+		free(buf);
+		return NULL;
+	}
+
+	return buf;
+}
+
+static bool
+replace_or_remove(bool failed, char const * temp,  char const * orig)
+{
+	if (failed) {
+		return remove(temp) < 0;
+	} else {
+		return rename(temp, orig) < 0;
+	}
+}
+
+static char *
+prepare_tmp_path(const char * const base)
+{
+	const size_t buflen = strlen(base) + 12;
+	char * const rv = malloc(buflen);
+
+	if (!rv)
+		return NULL;
+
+	if (snprintf(rv, buflen, "%s.tmp.XXXXXX", base) != buflen - 1) {
+		free(rv);
+		return NULL;
+	}
+
+	return rv;
+}
+
+static int
+update_matching(FILE * f, char const * const key, unsigned long value)
+{
+	const size_t keylen = strlen(key),
+	             valuelen = digits(++value);
+
+	return (fprintf(f, "%s=%lu\n", key, value) < keylen + 2 + valuelen) ? -1 : value;
+}
+
+static int
+ignore_matching(
+		FILE * f UNUSED_PARAM,
+		char const * const key UNUSED_PARAM,
+		unsigned long value UNUSED_PARAM)
+{
+	return 0;
+}
+
+static int
+return_matching(
+		FILE * f UNUSED_PARAM,
+		char const * const key UNUSED_PARAM,
+		unsigned long value)
+{
+	return value;
+}
+
+static int
+write_old(FILE * f, char const * const line, size_t const linelen)
+{
+	return (fwrite(line, linelen, 1, f) < 1) ? -1 : 0;
+}
+
+static int
+write_new(FILE * f, char const * const key)
+{
+	return (fprintf(f, "%s=1\n", key) != strlen(key) + 3) ? -1 : 1;
+}
+
+static int
+ignore_new(FILE * f UNUSED_PARAM, char const * const key UNUSED_PARAM)
+{
+	return 0;
+}
+
+static int
+parse_stream(
+		FILE * src,
+		FILE * dst,
+		const char * const key,
+		int (* const match_handler) (FILE *, char const * const, unsigned long),
+		int (* const miss_handler) (FILE *, char const * const, size_t),
+		int (* const match_not_found_handler) (FILE *, char const * const))
+{
+	int rv = 0;
+
+	char * pattern = prepare_pattern(key);
+
+	if (!pattern) {
+		rv = -1;
+	} else {
+		char * line = NULL;
+		ssize_t linelen = 0;
+		unsigned long val = -1lu;
+
+		while ((linelen = getline(&line, (size_t[]){0}, src)) >= 0) {
+			if (val != -1lu || sscanf(line, pattern, &val) != 1) {
+				if (miss_handler && miss_handler(dst, line, linelen) < 0) {
+					rv = -1;
+					break;
+				}
+			} else {
+				if ((rv = match_handler(dst, key, val)) < 0) {
+					break;
+				}
+			}
+		}
+
+		if (linelen < 0 && feof(src) && val == -1 && match_not_found_handler) {
+			rv = match_not_found_handler(dst, key);
+		}
+
+		free(pattern);
+	}
+
+	return rv;
+}
+
+static int
+parse_const_file(
+		const char * const src,
+		const char * const key,
+		int (* const match_handler) (FILE *, char const * const, unsigned long),
+		int (* const miss_handler) (FILE *, char const * const, size_t),
+		int (* const match_not_found_handler) (FILE *, char const * const))
+{
+	int rv = 0;
+	FILE * src_stream = fopen(src, "r");
+
+	if (!src_stream) {
+		if (errno == ENOENT && match_not_found_handler) {
+			rv = match_not_found_handler(NULL, key);
+		}
+	} else {
+		int src_stream_fd = fileno(src_stream);
+
+		if (src_stream_fd < 0 || flock(src_stream_fd, LOCK_SH) < 0) {
+			rv = -1;
+		} else {
+			rv = parse_stream(
+					src_stream,
+					NULL,
+					key,
+					match_handler,
+					miss_handler,
+					match_not_found_handler);
+		}
+
+		if (fclose(src_stream) < 0) {
+			rv = -1;
+		}
+	}
+
+	return rv;
+}
+
+static bool
+enforce_permissions(FILE * handle)
+{
+	return fchmod(fileno(handle), S_IRUSR | S_IWUSR) == -1;
+}
+
+static int
+parse_file(
+		const char * const src,
+		const char * const key,
+		int (* const match_handler) (FILE *, char const * const, unsigned long),
+		int (* const miss_handler) (FILE *, char const * const, size_t),
+		int (* const match_not_found_handler) (FILE *, char const * const))
+{
+	int rv = 0;
+	char * tmp_path = prepare_tmp_path(src);
+	FILE * src_stream,
+		 * tmp_stream;
+
+	if (!tmp_path) {
+		return -1;
+	}
+
+	tmp_stream = fopen(tmp_path, "w");
+
+	if (!tmp_stream) {
+		rv = -1;
+	} else if (enforce_permissions(tmp_stream)) {
+		rv = -1;
+	} else {
+		src_stream = fopen(src, "r");
+
+		if (!src_stream) {
+			if (errno == ENOENT && match_not_found_handler) {
+				rv = match_not_found_handler(tmp_stream, key);
+			}
+		} else {
+			int src_stream_fd = fileno(src_stream);
+
+			if (src_stream_fd < 0 || flock(src_stream_fd, LOCK_EX) < 0) {
+				rv = -1;
+			} else {
+				rv = parse_stream(
+						src_stream,
+						tmp_stream,
+						key,
+						match_handler,
+						miss_handler,
+						match_not_found_handler);
+			}
+
+			if (fclose(src_stream) < 0) {
+				rv = -1;
+			}
+		}
+
+		if (fclose(tmp_stream) < 0) {
+			rv = -1;
+		}
+
+		if (replace_or_remove(src_stream && rv < 0, tmp_path, src)) {
+			rv = -1;
+		}
+	}
+
+	free(tmp_path);
+	return rv;
+}
+
+int FAST_FUNC
+bb_tally_check(char const * const path, char const * const key)
+{
+	return parse_const_file(
+			path,
+			key,
+			return_matching,
+			NULL,
+			NULL);
+}
+
+int FAST_FUNC
+bb_tally_add(char const * const path, char const * const key)
+{
+	return parse_file(
+			path,
+			key,
+			update_matching,
+			write_old,
+			write_new);
+}
+
+int FAST_FUNC
+bb_tally_reset(char const * const path, char const * const key)
+{
+	return parse_file(
+			path,
+			key,
+			ignore_matching,
+			write_old,
+			ignore_new);
+}
+
+#if ENABLE_UNIT_TEST
+
+#define TEST_DB_PATH "tally_test.db"
+
+BBUNIT_DEFINE_TEST(test_digits)
+{
+	for (long l = 0, b = 10, d = 1; l < 1e15 ; l += 1 + l / 100) {
+		if (l >= b) {
+			b *= 10;
+			d++;
+		}
+
+		BBUNIT_ASSERT_EQ(digits(l), d);
+	}
+	BBUNIT_ENDTEST;
+}
+
+BBUNIT_DEFINE_TEST(test_tally_add)
+{
+	remove(TEST_DB_PATH);
+	for (int i = 0; i < 1000; i++) {
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), i + 1);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 2*i + 1);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "keke"  ), i + 1);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 2*i + 2);
+	}
+
+	for (int i = 0; i < 1000; i += 10) {
+		for (int k = 1 ; k <= 10 ; k++) {
+			for (int j = 0 ; j < 26 ; j++) {
+				BBUNIT_ASSERT_EQ(i + k,
+						bb_tally_add(TEST_DB_PATH, (char[]){'A' + j, '\0'}));
+			}
+		}
+	}
+
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "keke"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "barfoo"), 0);
+
+	for (int i = 0; i < 1000; i++) {
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), i + 1);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 2000 + i + 1);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "keke"), i + 1);
+	}
+
+	BBUNIT_ENDTEST;
+	remove(TEST_DB_PATH);
+}
+
+BBUNIT_DEFINE_TEST(test_tally_check)
+{
+	remove(TEST_DB_PATH);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), 0);
+
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "barfoo"), 0);
+
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), 0);
+
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 1);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 1);
+
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 1);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), 1);
+
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "barfoo"), 0);
+
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), 1);
+
+	for (int i = 0 ; i < 1000 ; i++) {
+		BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "keke"), i);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "keke"), i + 1);
+		BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "keke"), i + 1);
+	}
+
+	BBUNIT_ENDTEST;
+	remove(TEST_DB_PATH);
+}
+
+BBUNIT_DEFINE_TEST(test_tally_reset)
+{
+	remove(TEST_DB_PATH);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "keke"), 1);
+
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 1);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 1);
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 1);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 2);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 3);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 4);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 1);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 2);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 4);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), 2);
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), 0);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), 2);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), 3);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), 1);
+
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "keke"), 2);
+
+	BBUNIT_ENDTEST;
+	remove(TEST_DB_PATH);
+}
+
+BBUNIT_DEFINE_TEST(test_nonexisting_file)
+{
+	remove(TEST_DB_PATH);
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "non-existing-file"), 0);
+
+	remove(TEST_DB_PATH);
+	BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "non-existing-file"), 0);
+
+	remove(TEST_DB_PATH);
+	BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "non-existing-file"), 1);
+
+	BBUNIT_ENDTEST;
+	remove(TEST_DB_PATH);
+}
+
+BBUNIT_DEFINE_TEST(test_tally_usage)
+{
+	for (int i = 0; i < 10; i++) {
+		BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), i);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), i + 1);
+	}
+
+	for (int i = 0; i < 10; i++) {
+		BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "barfoo"), i);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "barfoo"), i + 1);
+	}
+
+	BBUNIT_ASSERT_EQ(bb_tally_reset(TEST_DB_PATH, "foobar"), 0);
+
+	for (int i = 0; i < 10; i++) {
+		BBUNIT_ASSERT_EQ(bb_tally_check(TEST_DB_PATH, "foobar"), i);
+		BBUNIT_ASSERT_EQ(bb_tally_add(TEST_DB_PATH, "foobar"), i + 1);
+	}
+
+	BBUNIT_ENDTEST;
+	remove(TEST_DB_PATH);
+}
+
+#endif
-- 
2.30.2



More information about the busybox mailing list