On 03.02.2023 19:45, Martin Kletzander wrote:
On Mon, Jan 30, 2023 at 09:00:01PM +0600, Oleg Vasilev wrote:
> Before, logs from deleted machines have been piling up, since there were
> no garbage collection mechanism. Now, virtlogd can be configured to
> periodically scan the log folder for orphan logs with no recent
> modifications
> and delete it.
>
> A single chain of recent and rotated logs is deleted in a single
> transaction.
>
> Signed-off-by: Oleg Vasilev <oleg.vasilev(a)virtuozzo.com>
> ---
> po/POTFILES | 1 +
> src/logging/log_cleaner.c | 268 ++++++++++++++++++++++++++++++++++++++
> src/logging/log_cleaner.h | 29 +++++
> src/logging/log_handler.h | 2 +
> src/logging/meson.build | 1 +
> 5 files changed, 301 insertions(+)
> create mode 100644 src/logging/log_cleaner.c
> create mode 100644 src/logging/log_cleaner.h
>
> diff --git a/po/POTFILES b/po/POTFILES
> index 169e2a41dc..2fb6d18e27 100644
> --- a/po/POTFILES
> +++ b/po/POTFILES
> @@ -123,6 +123,7 @@ src/locking/lock_driver_lockd.c
> src/locking/lock_driver_sanlock.c
> src/locking/lock_manager.c
> src/locking/sanlock_helper.c
> +src/logging/log_cleaner.c
> src/logging/log_daemon.c
> src/logging/log_daemon_dispatch.c
> src/logging/log_handler.c
> diff --git a/src/logging/log_cleaner.c b/src/logging/log_cleaner.c
> new file mode 100644
> index 0000000000..3de54d0795
> --- /dev/null
> +++ b/src/logging/log_cleaner.c
> @@ -0,0 +1,268 @@
> +/*
> + * log_cleaner.c: cleans obsolete log files
> + *
> + * Copyright (C) 2022 Virtuozzo International GmbH
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; If not, see
> + * <
http://www.gnu.org/licenses/>.
> + */
> +
> +#include <config.h>
> +
> +#include "log_cleaner.h"
> +#include "log_handler.h"
> +
> +#include "virerror.h"
> +#include "virobject.h"
> +#include "virfile.h"
> +#include "viralloc.h"
> +#include "virlog.h"
> +#include "virrotatingfile.h"
> +#include "virstring.h"
> +
> +#define VIR_FROM_THIS VIR_FROM_LOGGING
> +
> +VIR_LOG_INIT("logging.log_cleaner");
> +
> +/* Cleanup log root (/var/log/libvirt) and all subfolders (e.g.
> /var/log/libvirt/qemu) */
> +#define CLEANER_LOG_DEPTH 1
> +#define CLEANER_LOG_TIMEOUT_MS (24 * 3600 * 1000) /* One day */
> +#define MAX_TIME ((time_t) G_MAXINT64)
> +
> +static GRegex *log_regex;
> +
> +typedef struct _virLogCleanerChain virLogCleanerChain;
> +struct _virLogCleanerChain {
> + int rotated_max_index;
> + time_t last_modified;
> +};
> +
> +typedef struct _virLogCleanerData virLogCleanerData;
> +struct _virLogCleanerData {
> + virLogHandler *handler;
> + time_t oldest_to_keep;
> + GHashTable *chains;
> +};
> +
> +static const char*
This does not return a const char *, just char *, also the space is off.
> +virLogCleanerParseFilename(const char *path,
> + int *rotated_index)
> +{
> + g_autoptr(GMatchInfo) matchInfo = NULL;
> + g_autofree const char *rotated_index_str = NULL;
> + g_autofree const char *clear_path = NULL;
> + const char *chain_prefix = NULL;
None of these is const.
Just to educate myself, why are these not const? These are only set and
not changed.
There is of course the issue with type erasure, which requires the cast,
but that I consider the limitation of GHashTable API. Or should I never
attempt to put a const value into a type-erased void*?
Also, I see a number of tasks failed in a pipeline because of missing
unlink definition. Probably I forgot #include <unistd.h>. Should have I
tested the patch somehow else before submitting, apart from running test
on the machine I had at hand, e.g., have my own GitLab pipeline setup?
Thanks,
Oleg
> +
> + clear_path = realpath(path, NULL);
> + if (!clear_path) {
> + VIR_WARN("Failed to resolve path %s: %s", path,
> g_strerror(errno));
> + return NULL;
> + }
> +
> + if (!g_regex_match(log_regex, path, 0, &matchInfo))
> + return NULL;
> +
> + chain_prefix = g_match_info_fetch(matchInfo, 1);
> + if (!rotated_index)
> + return chain_prefix;
> +
> + *rotated_index = 0;
> + rotated_index_str = g_match_info_fetch(matchInfo, 3);
> +
> + if (!rotated_index_str)
> + return chain_prefix;
> +
> + if (virStrToLong_i(rotated_index_str, NULL, 10, rotated_index) <
> 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("Failed to parse rotated index from
'%s'"),
> + rotated_index_str);
> + return NULL;
> + }
> + return chain_prefix;
> +}
> +
> +static void
> +virLogCleanerProcessFile(virLogCleanerData *data,
> + const char *path,
> + struct stat *sb)
> +{
> + int rotated_index = 0;
> + g_autofree const char *chain_prefix = NULL;
> + virLogCleanerChain *chain;
> +
> + if (!S_ISREG(sb->st_mode))
> + return;
> +
> + chain_prefix = virLogCleanerParseFilename(path, &rotated_index);
> +
> + if (!chain_prefix)
> + return;
> +
> + if (rotated_index > data->handler->config->max_backups) {
> + if (unlink(path) < 0) {
> + VIR_WARN("Unable to delete %s: %s", path,
> g_strerror(errno));
> + }
There's a better function for this you are adding later.
> + return;
> + }
> +
> + chain = g_hash_table_lookup(data->chains, chain_prefix);
> +
> + if (!chain) {
> + chain = g_new0(virLogCleanerChain, 1);
> + g_hash_table_insert(data->chains, g_steal_pointer((gpointer*)
> &chain_prefix), chain);
With the removal of the consts this cast can be removed.
> + }
> +
> + chain->last_modified = MAX(chain->last_modified, sb->st_mtime);
> + chain->rotated_max_index = MAX(chain->rotated_max_index,
> + rotated_index);
> +}
> +
> +static GHashTable*
Missing space before asterisk.
> +virLogCleanerCreateTable(virLogHandler *handler)
> +{
> + /* HashTable: (const char*) chain_prefix -> (virLogCleanerChain*)
> chain */
> + GHashTable *chains = g_hash_table_new_full(g_str_hash, g_str_equal,
> + g_free, g_free);
> + size_t i;
> + virLogHandlerLogFile *file;
> + const char *chain_prefix;
Not a const.
> + virLogCleanerChain *chain;
> + VIR_LOCK_GUARD lock = virObjectLockGuard(handler);
> +
> + for (i = 0; i < handler->nfiles; i++) {
> + file = handler->files[i];
> + chain_prefix =
> virLogCleanerParseFilename(virRotatingFileWriterGetPath(file->file),
> + NULL);
> + if (!chain_prefix)
> + continue;
> +
> + chain = g_new0(virLogCleanerChain, 1);
> + chain->last_modified = MAX_TIME; /* Here we set MAX_TIME to
> the currently
> + * opened files to prevent
> its deletion. */
> + g_hash_table_insert(chains, (void *) chain_prefix, chain);
Again, cast can be removed.
> + }
> +
> + return chains;
> +}
> +
> +static void
> +virLogCleanerProcessFolder(virLogCleanerData *data,
> + const char *path,
> + int depth_left)
> +{
> + DIR *dir;
> + struct dirent *entry;
> + struct stat sb;
> +
> + if (virDirOpenIfExists(&dir, path) < 0)
> + return;
> +
> + while (virDirRead(dir, &entry, path) > 0) {
> + g_autofree char *newpath = NULL;
> +
> + newpath = g_strdup_printf("%s/%s", path, entry->d_name);
> +
> + if (stat(newpath, &sb) < 0) {
> + VIR_WARN("Unable to stat %s: %s", newpath,
> g_strerror(errno));
> + continue;
> + }
> +
> + if (S_ISDIR(sb.st_mode)) {
> + if (depth_left > 0)
> + virLogCleanerProcessFolder(data, newpath, depth_left
> - 1);
> + continue;
> + }
> +
> + virLogCleanerProcessFile(data, newpath, &sb);
> + }
> +
> + virDirClose(dir);
> +}
> +
> +static void
> +virLogCleanerDeleteFile(char *path)
> +{
> + int rc;
Pointless variable.
> + if ((rc = unlink(path)) < 0 && errno != ENOENT)
> + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno));
> + VIR_FREE(path);
> +}
> +
> +static void
> +virLogCleanerChainCB(gpointer key,
> + gpointer value,
> + gpointer user_data)
> +{
> + const char* chain_prefix = key;
Not const.
> + virLogCleanerChain* chain = value;
> + virLogCleanerData* data = user_data;
Spacing is off.
Rest is fine, I'll adjust the above before pushing.