[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