src_tests_libdlog_pipe_SOURCES = src/tests/libdlog_pipe.c src/libdlog/log_pipe.c src/shared/logcommon.c src/shared/logconfig.c src/shared/queued_entry.c src/shared/translate_syslog.c src/shared/parsers.c
src_tests_libdlog_pipe_CFLAGS = $(check_CFLAGS) -pthread
-src_tests_libdlog_pipe_LDFLAGS = $(AM_LDFLAGS) -lpthread -Wl,--wrap=syslog_critical_failure,--wrap=connect,--wrap=write,--wrap=recv_pipe,--wrap=dup2,--wrap=socket -lm
+src_tests_libdlog_pipe_LDFLAGS = $(AM_LDFLAGS) -lpthread -Wl,--wrap=syslog_critical_failure,--wrap=connect,--wrap=write,--wrap=recv_pipe,--wrap=dup2,--wrap=socket,--wrap=close -lm
src_tests_libdlog_android_SOURCES = src/tests/libdlog_android.c src/libdlog/log_android.c src/shared/logcommon.c src/shared/parsers.c
src_tests_libdlog_android_CFLAGS = $(check_CFLAGS)
struct log_config;
extern int (*write_to_log)(log_id_t, log_priority, const char *tag, const char *msg);
+extern void (*destroy_backend)();
+
extern pthread_rwlock_t log_limiter_lock;
extern bool limiter;
#ifdef UNIT_TEST
void __dlog_fini(void);
+void __dlog_destroy(void);
void __configure(void);
#endif
* @see __dlog_init_backend
*/
int (*write_to_log)(log_id_t log_id, log_priority prio, const char *tag, const char *msg) = NULL;
+void (*destroy_backend)();
+
pthread_rwlock_t log_limiter_lock = PTHREAD_RWLOCK_INITIALIZER;
+static pthread_rwlock_t log_destruction_lock = PTHREAD_RWLOCK_INITIALIZER;
+
extern void __dlog_init_pipe(const struct log_config *conf);
extern void __dlog_init_android(const struct log_config *conf);
return DLOG_ERROR_NONE;
}
-static int __write_to_log(log_id_t log_id, int prio, const char *tag, const char *fmt, va_list ap, bool check_should_log)
+static int __write_to_log_critical_section(log_id_t log_id, int prio, const char *tag, const char *fmt, va_list ap, bool check_should_log)
{
+ if ((check_should_log || limiter_apply_to_all_buffers) && (dlog_check_limiter(log_id, prio, tag) < 0))
+ return DLOG_ERROR_NONE;
+
char buf[LOG_MAX_PAYLOAD_SIZE];
+ vsnprintf(buf, sizeof buf, fmt, ap);
+ return write_to_log(log_id, prio, tag, buf);
+}
+static int __write_to_log(log_id_t log_id, int prio, const char *tag, const char *fmt, va_list ap, bool check_should_log)
+{
int ret = dlog_check_validity(log_id, prio, tag);
if (ret < 0)
return ret;
- if (!write_to_log)
- return DLOG_ERROR_NOT_PERMITTED;
-
- /* if limiter_apply_to_all_buffers config variable is set to 1,
- * check_should_log value does not matter and the entry is always
- * tested against all conditions, i.e. limiter rules
- */
- ret = (check_should_log || limiter_apply_to_all_buffers) ? dlog_check_limiter(log_id, prio, tag) : 0;
-
- if (ret < 0)
- return DLOG_ERROR_NONE;
-
- vsnprintf(buf, sizeof buf, fmt, ap);
+ /* The only thing that needs to be protected here is `write_to_log` since
+ * all other resources already have their own specific locks (and even the
+ * pointer could be made to point at a null handler instead of a true NULL)
+ * but giving this guarantee makes everything a lot simpler as it removes
+ * the risk of something suddenly becoming NULL during processing. */
+ pthread_rwlock_rdlock(&log_destruction_lock);
+ ret = write_to_log ? __write_to_log_critical_section(log_id, prio, tag, fmt, ap, check_should_log) : DLOG_ERROR_NOT_PERMITTED;
+ pthread_rwlock_unlock(&log_destruction_lock);
- return write_to_log(log_id, prio, tag, buf);
+ return ret;
}
/**
return ret;
}
-#ifdef UNIT_TEST
/**
* @brief Finalize DLog
* @details Finalizes and deallocates the library
- * @notes Used in tests only
+ * @notes Used directly in tests; brings back the pre-init state
*/
+#ifndef UNIT_TEST
+static
+#endif
void __dlog_fini(void)
{
+ if (destroy_backend) {
+ destroy_backend();
+ destroy_backend = NULL;
+ }
+ write_to_log = NULL;
+
__log_limiter_destroy();
__dynamic_config_destroy();
}
+
+#ifdef UNIT_TEST
+void
+#else
+static void __attribute__((destructor))
#endif
+__dlog_destroy(void)
+{
+ pthread_rwlock_wrlock(&log_destruction_lock);
+ __dlog_fini();
+
+ /* IMPORTANT! The lock is NEVER RELEASED. This is done ON PURPOSE.
+ * The critical section can still be reached in some ways, NONE LEGAL.
+ *
+ * The first is that the program links the library dynamically and keeps
+ * pointers to API functions past dlclose(). This is UNDEFINED BEHAVIOUR
+ * and the implementation is NOT REQUIRED to keep the functions in memory
+ * AT ALL and doing this COULD HAVE ALREADY CRASHED the program under a
+ * different implementation.
+ *
+ * The second is when linking statically and FAILING TO JOIN threads before
+ * exit(). These threads then typically keep running and can access the
+ * library interface. However, they WILL DIE ANY MOMENT NOW ANYWAY so
+ * getting deadlocked is of no consequence.
+ *
+ * In theory it would be possible to detect that destruction has already
+ * taken place and reinitialize the library to salvage a logging attempt.
+ * This is a HORRIBLE IDEA since without a destructor to rely on, either
+ * those RESOURCES WOULD LEAK or we would have to MASSIVELY CONVOLUTE our
+ * logic for manual destruction in such cases. */
+}
return ret;
}
+static void __destroy_androidlogger()
+{
+ /* No need for an explicit lock here; this should only be called
+ * by the library destructor which has its own lock */
+
+ for (size_t i = 0; i < NELEMS(log_fds); ++i)
+ if (log_fds[i] >= 0)
+ close(log_fds[i]);
+}
+
/**
* @brief Initialize the backend
* @details Prepares the backend for proper and fruitful work
continue;
}
write_to_log = __write_to_log_android;
+ destroy_backend = __destroy_androidlogger;
return;
failure:
return ret;
}
+static void __destroy_pipe()
+{
+ /* No need for an explicit lock here; this should only be called
+ * by the library destructor which has its own lock */
+
+ for (size_t i = 0; i < NELEMS(pipe_fd); ++i)
+ if (pipe_fd[i] >= 0)
+ close(pipe_fd[i]);
+
+ wait_pipe_ms = DEFAULT_WAIT_PIPE_MS;
+ signal(SIGPIPE, SIG_DFL);
+}
+
/**
* @brief Initialize the backend
* @details Prepares the backend for proper and fruitful work
snprintf(log_pipe_path[i], PATH_MAX, "%s", conf_val);
}
write_to_log = __write_to_log_pipe;
+ destroy_backend = __destroy_pipe;
}
#include <logconfig.h>
int (*write_to_log)(log_id_t, log_priority, const char *tag, const char *msg) = NULL;
+void (*destroy_backend)() = NULL;
extern void __dlog_init_android(const struct log_config *conf);
static struct log_config CONF;
__dlog_init_android(&CONF);
assert(closed == 2);
assert(!write_to_log);
+ assert(!destroy_backend);
closed = 0;
fail_open = false;
__dlog_init_android(&CONF);
assert(closed == 0);
assert(write_to_log);
+ assert(destroy_backend);
#define F(B, P, M) assert(write_to_log(B, P, "tag", M) == DLOG_ERROR_INVALID_PARAMETER)
F(LOG_ID_INVALID, DLOG_ERROR , "xx");
used_msg = LONG_TEXTS[0];
writev_ret = 1234;
assert(write_to_log(0, 3, NULL, LONG_TEXTS[0]) == 1234);
+
+ destroy_backend();
+ assert(closed + 1 == LOG_ID_MAX);
}
#include <logconfig.h>
int (*write_to_log)(log_id_t, log_priority, const char *tag, const char *msg) = NULL;
-pthread_rwlock_t log_limiter_lock = PTHREAD_RWLOCK_INITIALIZER;
+void (*destroy_backend)() = NULL;
+
extern void __dlog_init_pipe(const struct log_config *conf);
int errno_to_set;
return 17;
}
+static size_t closed;
+int __wrap_close(int fd)
+{
+ assert(fd == 1337 || fd == 17);
+ ++closed;
+}
+
int main()
{
struct log_config conf = {NULL, NULL};
__dlog_init_pipe(&conf);
assert(critical_failure_detected);
assert(!write_to_log);
+ assert(!destroy_backend);
critical_failure_detected = false;
pipe_path[0] = '/';
__dlog_init_pipe(&conf);
assert(critical_failure_detected);
assert(!write_to_log);
+ assert(!destroy_backend);
critical_failure_detected = false;
pipe_path[4] = '\0';
__dlog_init_pipe(&conf);
assert(!critical_failure_detected);
assert(write_to_log);
+ assert(destroy_backend);
#define ACTUAL_PAYLOAD_SIZE (LOG_MAX_PAYLOAD_SIZE - 5) // 1 priority, 3 tag "xx\0", 1 null terminator
char payload[ACTUAL_PAYLOAD_SIZE + 1] = { [0 ... ACTUAL_PAYLOAD_SIZE] = 'x' };
// NULL tag is fine, apparently.
TC(0, 0, 0, 45, 301, NULL, 0, 1, 0);
#undef TC
+
+ closed = 0;
+ destroy_backend();
+ assert(closed == 1); // just MAIN, we'd never used the others
}