From: Hemmo Nieminen <[email protected]>

Signed-off-by: Hemmo Nieminen <[email protected]>
---
 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

_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to