aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichał Łyszczek <michal.lyszczek@bofc.pl>2019-03-29 09:42:34 +0100
committerMichał Łyszczek <michal.lyszczek@bofc.pl>2019-03-29 09:47:22 +0100
commit848cecff38b3e90de08ea7a5c47e0cb71447e34b (patch)
treee4e59f67cc1710d4094efc1ecb396ea112ca348c
parent0ed827803b6a283915b6228902a26b411a569ae5 (diff)
downloadembedlog-848cecff38b3e90de08ea7a5c47e0cb71447e34b.tar.gz
embedlog-848cecff38b3e90de08ea7a5c47e0cb71447e34b.tar.bz2
embedlog-848cecff38b3e90de08ea7a5c47e0cb71447e34b.zip
el_flush(): add new function to force data from buffers to deviceinnogy
Signed-off-by: Michał Łyszczek <michal.lyszczek@bofc.pl>
-rw-r--r--embedlog-sources.mk1
l---------examples/el-flush.c1
-rw-r--r--include/embedlog.h2
-rw-r--r--man/Makefile.am4
-rw-r--r--man/el_cleanup.32
-rw-r--r--man/el_flush.380
-rw-r--r--man/el_init.32
-rw-r--r--man/el_oflush.31
-rw-r--r--man/el_option.32
-rw-r--r--man/el_overview.76
-rw-r--r--man/el_print.32
-rw-r--r--src/el-file.c163
-rw-r--r--src/el-flush.c104
-rw-r--r--src/el-private.h1
l---------tst/el-flush.c1
-rw-r--r--tst/test-el-file.c15
16 files changed, 318 insertions, 69 deletions
diff --git a/embedlog-sources.mk b/embedlog-sources.mk
index ff750ec..c60f5ea 100644
--- a/embedlog-sources.mk
+++ b/embedlog-sources.mk
@@ -4,6 +4,7 @@ embedlog_sources = el-options.c \
el-print.c \
el-puts.c \
el-ts.c \
+ el-flush.c \
snprintf.c
if ENABLE_OUT_FILE
diff --git a/examples/el-flush.c b/examples/el-flush.c
new file mode 120000
index 0000000..3291726
--- /dev/null
+++ b/examples/el-flush.c
@@ -0,0 +1 @@
+../src/el-flush.c \ No newline at end of file
diff --git a/include/embedlog.h b/include/embedlog.h
index dd86c12..70b2f8a 100644
--- a/include/embedlog.h
+++ b/include/embedlog.h
@@ -193,6 +193,7 @@ int el_perror(const char *file, size_t line, const char *func,
int el_putb(const void *memory, size_t mlen);
int el_pbinary(enum el_level level, const void *memory, size_t mlen);
const struct el_options *el_get_options(void);
+int el_flush(void);
int el_oinit(struct el_options *options);
int el_ocleanup(struct el_options *options);
@@ -214,6 +215,7 @@ int el_operror(const char *file, size_t line, const char *func,
int el_oputb(struct el_options *options, const void *memory, size_t mlen);
int el_opbinary(enum el_level level, struct el_options *options,
const void *memory, size_t mlen);
+int el_oflush(struct el_options *options);
#ifdef __cplusplus
}
diff --git a/man/Makefile.am b/man/Makefile.am
index abfafb4..54a2186 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -14,6 +14,8 @@ man_MANS = el_cleanup.3 \
el_pmemory.3 \
el_print.3 \
el_puts.3 \
- el_vprint.3
+ el_vprint.3 \
+ el_flush.3 \
+ el_oflush.3
EXTRA_DIST = $(man_MANS)
diff --git a/man/el_cleanup.3 b/man/el_cleanup.3
index 082c10f..04dfd5e 100644
--- a/man/el_cleanup.3
+++ b/man/el_cleanup.3
@@ -45,6 +45,8 @@ Passed options object is not valid
.BR el_overview (7),
.BR el_option (3),
.BR el_puts (3),
+.BR el_flush (3),
+.BR el_oflish (3),
.BR el_print (3),
.BR el_vprint (3),
.BR el_perror (3),
diff --git a/man/el_flush.3 b/man/el_flush.3
new file mode 100644
index 0000000..f8d37b5
--- /dev/null
+++ b/man/el_flush.3
@@ -0,0 +1,80 @@
+.TH "el_flush" "3" "28 February 2019 (v0.5.0)" "bofc.pl"
+.SH NAME
+.PP
+.BR el_flush ,\ el_oflush
+- flush logs from all buffers to underlying devices.
+.SH SYNOPSIS
+.PP
+.BI "#include <embedlog.h>"
+.PP
+.BI "int el_flush(void)"
+.br
+.BI "int el_oflush(struct el_options *" options ")"
+.SH DESCRIPTION
+.PP
+.B embedlog
+(and underlying drivers, operating systems, libraries etc) to improve
+performance, may buffer message before commiting changes to actual device.
+And it's ok, there is really no need to waste resources every time you want to
+log 50 bytes of debug print on commiting message physically to the drive - or
+send via network.
+It's much more common to buffer messages in RAM until certain ammount of bytes
+are ready to send - and then send a bunch of data in one quick burst.
+But that behaviour is not always desired, like in situation in which we know we
+won't be printing logs for long time.
+For such situation explicit flush may in order.
+.PP
+.BR el_flush (3)
+flushes logs from all buffers to underlying device.
+For example, when logs are printed to file, logs are written through buffered
+.B stdio
+functions, which may result in some logs never being actually put into block
+device in case of abnormal application crash - that of course depends on
+settings like
+.BR EL_FSYNC_EVERY .
+This function will do everything what is in its power to make sure logs landed
+on underlying device.
+This applies to files, but also to logs with network output.
+.PP
+.BR el_oflush (3)
+works just the same, but accepts custom
+.I options
+as argument.
+.SH RETURN VALUE
+.PP
+Functions return 0 upon successful flush or -1 on errors.
+When -1 was returned there is no guarantee that data has landed on underlying
+device.
+.SH ERRORS
+.PP
+.BR el_flush (3)
+and
+.BR el_oflush (3)
+may return errors from functions like:
+.BR fflush (),\ fileno (),\ fsync (),\ fseek (),\ ftell (),\ fopen ()
+and/or
+.BR fclose ().
+.PP
+.BR el_oflush (3)
+may additionally return:
+.TP
+.B EINVAL
+Passed options object is not valid
+.SH SEE ALSO
+.PP
+.BR el_init (3),
+.BR el_overview (7),
+.BR el_option (3),
+.BR el_puts (3),
+.BR el_print (3),
+.BR el_vprint (3),
+.BR el_perror (3),
+.BR el_pmemory (3),
+.BR el_pmemory_table (3),
+.BR el_opmemory_table (3),
+.BR el_ooption (3),
+.BR el_oputs (3),
+.BR el_oprint (3),
+.BR el_ovprint (3),
+.BR el_operror (3),
+.BR el_opmemory (3).
diff --git a/man/el_init.3 b/man/el_init.3
index bc260f5..934de6c 100644
--- a/man/el_init.3
+++ b/man/el_init.3
@@ -108,6 +108,8 @@ Note: error handling has been ommited for clarity sake
.BR el_overview (7),
.BR el_option (3),
.BR el_puts (3),
+.BR el_flush (3),
+.BR el_oflish (3),
.BR el_print (3),
.BR el_vprint (3),
.BR el_perror (3),
diff --git a/man/el_oflush.3 b/man/el_oflush.3
new file mode 100644
index 0000000..0f1a0a9
--- /dev/null
+++ b/man/el_oflush.3
@@ -0,0 +1 @@
+.so man3/el_flush.3
diff --git a/man/el_option.3 b/man/el_option.3
index c5218b4..abd1d53 100644
--- a/man/el_option.3
+++ b/man/el_option.3
@@ -645,6 +645,8 @@ informations
.BR el_cleanup (3),
.BR el_overview (7),
.BR el_puts (3),
+.BR el_flush (3),
+.BR el_oflish (3),
.BR el_print (3),
.BR el_vprint (3),
.BR el_perror (3),
diff --git a/man/el_overview.7 b/man/el_overview.7
index c84542d..1cd4ffa 100644
--- a/man/el_overview.7
+++ b/man/el_overview.7
@@ -94,6 +94,8 @@ size_t " mlen ")
.BI "int el_pbinary(enum el_level " level ", const void *" memory ", \
size_t " mlen ")
.br
+.BI "int el_flush(void)"
+.br
.B const struct el_options *el_get_options(void)
.PP
Each functions has its equivalent function but accepting
@@ -139,6 +141,8 @@ const void *" memory ", size_t " mlen ")"
.br
.BI "int el_opbinary(enum el_level " level ", struct el_options *" options ", \
const void *" memory ", size_t " mlen ")"
+.br
+.BI "int el_oflush(struct el_options *" options ")"
.PP
For more information about a function open manual page with functions name from
section 3 (ie. for el_oputs, you'd open
@@ -257,6 +261,8 @@ for more details.
.BR el_cleanup (3),
.BR el_option (3),
.BR el_puts (3),
+.BR el_flush (3),
+.BR el_oflish (3),
.BR el_print (3),
.BR el_vprint (3),
.BR el_perror (3),
diff --git a/man/el_print.3 b/man/el_print.3
index 4ab8709..bd4026f 100644
--- a/man/el_print.3
+++ b/man/el_print.3
@@ -507,6 +507,8 @@ and if file rotation is enabled also from
.BR el_cleanup (3),
.BR el_overview (7),
.BR el_option (3),
+.BR el_flush (3),
+.BR el_oflish (3),
.BR el_pmemory (3),
.BR el_pmemory_table (3),
.BR el_opmemory_table (3),
diff --git a/src/el-file.c b/src/el-file.c
index 2b44954..cd8de68 100644
--- a/src/el-file.c
+++ b/src/el-file.c
@@ -545,6 +545,97 @@ int el_file_open
/* ==========================================================================
+ Does whatever it takes to make sure that logs are flushed from any
+ buffers to physical disk. It's not always possible, but boy, do we try.
+ ========================================================================== */
+
+
+int el_file_flush
+(
+ struct el_options *options /* printing options */
+)
+{
+ /* file store operation has particular issue. You can like, write
+ * hundreds of megabytes into disk and that data indeed will land
+ * into your block device *but* metadata will not! That is if power
+ * lose occurs (even after those hundreds of megs), file metadata
+ * might not be updated and we will lose whole, I say it again
+ * *whole* file. To prevent data lose on power down we try to sync
+ * file and its metadata to block device. We do this every
+ * configured "sync_every" field since syncing every single write
+ * would simply kill performance - its up to user to decide how much
+ * data he is willing to lose. It is also possible that altough we
+ * sync our file and metadata, parent directory might not get
+ * flushed and there will not be entry for our file - meaning file
+ * will be lost too, but such situations are ultra rare and there
+ * isn't really much we can do about it here but praying.
+ */
+
+#if HAVE_FSYNC && HAVE_FILENO
+ int fd; /* systems file descriptor for options->file */
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+#endif /* HAVE_FSYNC && HAVE_FILENO */
+
+ VALID(EINVAL, options);
+ VALID(EBADF, options->current_log);
+ VALID(EBADF, options->current_log[0] != '\0');
+
+ /*
+ * first flush data from stdio library buffers into kernel
+ */
+
+ if (fflush(options->file) != 0)
+ {
+ return -1;
+ }
+
+#if HAVE_FSYNC && HAVE_FILENO
+ /*
+ * and then sync data into block device
+ */
+
+ if ((fd = fileno(options->file)) < 0)
+ {
+ return -1;
+ }
+
+ if (fsync(fd) != 0)
+ {
+ return -1;
+ }
+#else /* HAVE_FSYNC && HAVE_FILENO */
+ /* if system does not implement fileno and fsync our only hope lies
+ * in closing (which should sync file to block device) and then
+ * opening file again. Yup, performance will suck, but hey, its
+ * about data safety!
+ */
+
+ fclose(options->file);
+
+ if ((options->file = fopen(options->current_log, "a")) == NULL)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ fseek(options->file, 0, SEEK_END);
+ options->fpos = ftell(options->file);
+#endif /* HAVE_FSYNC && HAVE_FILENO */
+
+#ifdef RUN_TESTS
+ file_synced = 1;
+#endif /* RUN_TESTS */
+
+ /* after syncing data, update written after sync field, so we
+ * don't trigger another flush right after this one
+ */
+
+ options->written_after_sync = 0;
+ return 0;
+}
+
+
+/* ==========================================================================
writes memory block pointed by mem of size mlen into a file, if needed
also rotates file
========================================================================== */
@@ -619,76 +710,12 @@ int el_file_putb
if (options->written_after_sync >= options->file_sync_every ||
options->level_current_msg <= options->file_sync_level)
{
- /*
- * file store operation has particular issue. You can like, write
- * hundreds of megabytes into disk and that data indeed will land
- * into your block device *but* metadata will not! That is if power
- * lose occurs (even after those hundreds of megs), file metadata
- * might not be updated and we will lose whole, I say it again
- * *whole* file. To prevent data lose on power down we try to sync
- * file and its metadata to block device. We do this every
- * configured "sync_every" field since syncing every single write
- * would simply kill performance - its up to user to decide how much
- * data he is willing to lose. It is also possible that altough we
- * sync our file and metadata, parent directory might not get
- * flushed and there will not be entry for our file - meaning file
- * will be lost too, but such situations are ultra rare and there
- * isn't really much we can do about it here but praying.
- */
-
-#if HAVE_FSYNC && HAVE_FILENO
- int fd; /* systems file descriptor for options->file */
- /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-#endif /* HAVE_FSYNC && HAVE_FILENO */
-
- /*
- * first flush data from stdio library buffers into kernel
- */
-
- if (fflush(options->file) != 0)
- {
- return -1;
- }
-
-#if HAVE_FSYNC && HAVE_FILENO
- /*
- * and then sync data into block device
- */
-
- if ((fd = fileno(options->file)) < 0)
- {
- return -1;
- }
-
- if (fsync(fd) != 0)
- {
- return -1;
- }
-#else /* HAVE_FSYNC && HAVE_FILENO */
- /*
- * if system does not implement fileno and fsync our only hope lies
- * in closing (which should sync file to block device) and then
- * opening file again. Yup, performance will suck, but hey, its
- * about data safety!
+ /* we either written enough bytes to trigger flush, or log
+ * level is high enough it triggers log flush to block
+ * device
*/
- fclose(options->file);
-
- if ((options->file = fopen(options->current_log, "a")) == NULL)
- {
- errno = EBADF;
- return -1;
- }
-
- fseek(options->file, 0, SEEK_END);
- options->fpos = ftell(options->file);
-#endif /* HAVE_FSYNC && HAVE_FILENO */
-
-#ifdef RUN_TESTS
- file_synced = 1;
-#endif /* RUN_TESTS */
-
- options->written_after_sync = 0;
+ return el_file_flush(options);
}
return 0;
diff --git a/src/el-flush.c b/src/el-flush.c
new file mode 100644
index 0000000..baba6df
--- /dev/null
+++ b/src/el-flush.c
@@ -0,0 +1,104 @@
+/* ==========================================================================
+ Licensed under BSD 2clause license See LICENSE file for more information
+ Author: Michał Łyszczek <michal.lyszczek@bofc.pl>
+ ==========================================================================
+ _____________________________________________________________
+ / here we handle el_flush function which should make sure all \
+ | logs are flushed from buffers and are received by |
+ \ underlying device /
+ -------------------------------------------------------------
+ \
+ \ \
+ \ /\
+ ( )
+ .( o ).
+ ==========================================================================
+ _ __ __ ____ _ __
+ (_)____ _____ / /__ __ ____/ /___ / __/(_)/ /___ _____
+ / // __ \ / ___// // / / // __ // _ \ / /_ / // // _ \ / ___/
+ / // / / // /__ / // /_/ // /_/ // __/ / __// // // __/(__ )
+ /_//_/ /_/ \___//_/ \__,_/ \__,_/ \___/ /_/ /_//_/ \___//____/
+
+ ========================================================================== */
+
+
+#include "el-private.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+
+/* ==========================================================================
+ __ __ _ ____
+ ____ __ __ / /_ / /(_)_____ / __/__ __ ____ _____ _____
+ / __ \ / / / // __ \ / // // ___/ / /_ / / / // __ \ / ___// ___/
+ / /_/ // /_/ // /_/ // // // /__ / __// /_/ // / / // /__ (__ )
+ / .___/ \__,_//_.___//_//_/ \___/ /_/ \__,_//_/ /_/ \___//____/
+ /_/
+ ========================================================================== */
+
+
+/* ==========================================================================
+ Does whatever it takes to make sure data is flushed from buffers and is
+ received by underlying devices (which may be block device or remote
+ server).
+ ========================================================================== */
+
+
+int el_flush
+(
+ void
+)
+{
+ return el_oflush(&g_options);
+}
+
+
+/* ==========================================================================
+ Same as el_flush() but takes options as argument
+ ========================================================================== */
+
+
+int el_oflush
+(
+ struct el_options *options /* options defining printing style */
+)
+{
+ int rv; /* return value from function */
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ VALID(EINVAL, options);
+ VALID(ENODEV, options->outputs != 0);
+
+ rv = 0;
+
+#if ENABLE_OUT_STDERR
+ if (options->outputs & EL_OUT_STDERR)
+ {
+ rv |= fflush(stderr);
+ }
+#endif
+
+#if ENABLE_OUT_STDERR
+ if (options->outputs & EL_OUT_STDOUT)
+ {
+ rv |= fflush(stdout);
+ }
+#endif
+
+#if ENABLE_OUT_FILE
+ if (options->outputs & EL_OUT_FILE)
+ {
+ rv |= el_file_flush(options);
+ }
+#endif
+
+#if 0
+ if (options->outputs & EL_OUT_NET)
+ {
+ el_net_flush(options);
+ }
+#endif
+
+ return rv;
+}
diff --git a/src/el-private.h b/src/el-private.h
index 1399e39..c36ecbe 100644
--- a/src/el-private.h
+++ b/src/el-private.h
@@ -292,6 +292,7 @@ int el_file_open(struct el_options *options);
int el_file_puts(struct el_options *options, const char *s);
int el_file_putb(struct el_options *options, const void *mem, size_t mlen);
void el_file_cleanup(struct el_options *options);
+int el_file_flush(struct el_options *options);
#endif
diff --git a/tst/el-flush.c b/tst/el-flush.c
new file mode 120000
index 0000000..3291726
--- /dev/null
+++ b/tst/el-flush.c
@@ -0,0 +1 @@
+../src/el-flush.c \ No newline at end of file
diff --git a/tst/test-el-file.c b/tst/test-el-file.c
index 371721a..852311b 100644
--- a/tst/test-el-file.c
+++ b/tst/test-el-file.c
@@ -1536,6 +1536,20 @@ static void file_sync_always(void)
========================================================================== */
+static void file_sync_via_flush_function(void)
+{
+ el_option(EL_FSYNC_EVERY, 16);
+ mt_fok(el_puts(s8));
+ mt_fail(file_synced == 0);
+ mt_fok(el_flush());
+ mt_fail(file_synced == 1);
+}
+
+
+/* ==========================================================================
+ ========================================================================== */
+
+
static void file_sync_periodic(void)
{
el_option(EL_FSYNC_EVERY, 8);
@@ -1693,6 +1707,7 @@ void el_file_test_group(void)
mt_run(file_sync_always);
mt_run(file_sync_periodic);
mt_run(file_sync_level);
+ mt_run(file_sync_via_flush_function);
rmdir(WORKDIR);
#endif