[patch] new feature for file destination: persistent symlinks

<[email protected]>
Newsgroups gmane.comp.syslog-ng
Message-ID <[email protected]>
Hi all,

I'm new here. I love syslog-ng: it's well written and documented and
each time I need some new feature, I dig up the documentation and I
find it's already implemented!

Like many others, I use macros in file destinations (say,
"/var/log/cron.${YEAR}${MONTH}"), but I also want to maintain a
persistent symlink to the current logfile (that would be
something like "/var/log/cron") in order to have "easier" access if I'm
grepping for something manually.

I couldn't find this feature in syslog-ng, so I wrote the code myself;
in the configuration file, it looks like this:

destination d_file_cron {
  file("/var/log/cron.${YEAR}${MONTH}" symlink_as("/var/log/cron"));
};

It works for me on my Slackware boxes. I'm sharing the patch I put
together, hoping that it may be useful to others and perhaps even
considered for inclusion in mainstream.

From a functional perspective, the "symlink_as" file inherits both
"create-dirs" and file ownership from its file destination (permissions
are not applicable to symlinks, at least on linux).

Patch applies cleanly to latest production release (3.33). I tried to
keep the original coding style for easier review (admittedly, the code
could have gone into the function that opens the file destination, but
I didn't want to change too much).

Thanks for the good work and happy logging!

Andrea.

______________________________________________________________________________
Member info: https://lists.balabit.hu/mailman/listinfo/syslog-ng
Documentation: http://www.balabit.com/support/documentation/?product=syslog-ng
FAQ: http://www.balabit.com/wiki/syslog-ng-faq
filedest-symlink-as.diff (text/x-patch, 8.3 KB)
--- a/lib/file-perms.c	2021-07-06 17:55:58.000000000 +0100
+++ b/lib/file-perms.c	2021-12-04 12:22:42.145929557 +0000
@@ -195,6 +195,20 @@
 }
 
 gboolean
+file_perm_options_apply_symlink(const FilePermOptions *self, const gchar *path)
+{
+#ifndef _MSC_VER
+  gboolean result = TRUE;
+
+  if (self->file_uid >= 0 && lchown(path, (uid_t) self->file_uid, -1) < 0)
+    result = FALSE;
+  if (self->file_gid >= 0 && lchown(path, -1, (gid_t) self->file_gid) < 0)
+    result = FALSE;
+  return result;
+#endif
+}
+
+gboolean
 file_perm_options_apply_dir(const FilePermOptions *self, const gchar *path)
 {
 #ifndef _MSC_VER
--- a/lib/file-perms.h	2021-07-06 17:55:58.000000000 +0100
+++ b/lib/file-perms.h	2021-12-04 12:18:38.983916571 +0000
@@ -56,6 +56,7 @@
 void file_perm_options_inherit_dont_change(FilePermOptions *self);
 
 gboolean file_perm_options_apply_file(const FilePermOptions *self, const gchar *name);
+gboolean file_perm_options_apply_symlink(const FilePermOptions *self, const gchar *name);
 gboolean file_perm_options_apply_dir(const FilePermOptions *self, const gchar *name);
 gboolean file_perm_options_apply_fd(const FilePermOptions *self, gint fd);
 gboolean file_perm_options_create_containing_directory(const FilePermOptions *self, const gchar *name);
--- a/modules/affile/affile-dest.c	2021-07-06 17:55:58.000000000 +0100
+++ b/modules/affile/affile-dest.c	2021-12-04 13:07:38.017073532 +0000
@@ -140,7 +140,8 @@
 
   msg_verbose("Initializing destination file writer",
               evt_tag_str("template", self->owner->filename_template->template),
-              evt_tag_str("filename", self->filename));
+              evt_tag_str("filename", self->filename),
+              evt_tag_str("symlink_as", self->owner->symlink_as));
 
   self->last_open_stamp = self->last_msg_stamp;
   if (self->owner->overwrite_if_older > 0 &&
@@ -156,6 +157,9 @@
   FileOpenerResult open_result = file_opener_open_fd(self->owner->file_opener, self->filename, AFFILE_DIR_WRITE, &fd);
   if (open_result == FILE_OPENER_RESULT_SUCCESS)
     {
+      if (self->owner->symlink_as != NULL)
+        file_opener_symlink(self->owner->file_opener, self->owner->symlink_as, self->filename);
+
       LogTransport *transport = file_opener_construct_transport(self->owner->file_opener, fd);
 
       proto = file_opener_construct_dst_proto(self->owner->file_opener, transport,
@@ -386,6 +390,15 @@
 }
 
 void
+affile_dd_set_symlink_as(LogDriver *s, const gchar *symlink_as)
+{
+  AFFileDestDriver *self = (AFFileDestDriver *) s;
+
+  g_free(self->symlink_as);
+  self->symlink_as = g_strdup(symlink_as);
+}
+
+void
 affile_dd_set_fsync(LogDriver *s, gboolean use_fsync)
 {
   AFFileDestDriver *self = (AFFileDestDriver *) s;
@@ -756,6 +769,7 @@
   log_writer_options_destroy(&self->writer_options);
   file_opener_options_deinit(&self->file_opener_options);
   file_opener_free(self->file_opener);
+  g_free(self->symlink_as);
   log_dest_driver_free(s);
 }
 
@@ -782,6 +796,8 @@
     }
   file_opener_options_defaults(&self->file_opener_options);
 
+  self->symlink_as = NULL;
+
   affile_dd_set_time_reap(&self->super.super, self->filename_is_a_template ? -1 : 0);
   g_static_mutex_init(&self->lock);
 
--- a/modules/affile/affile-dest.h	2021-07-06 17:55:58.000000000 +0100
+++ b/modules/affile/affile-dest.h	2021-12-03 19:11:40.931625812 +0000
@@ -47,6 +47,7 @@
   GHashTable *writer_hash;
 
   gint overwrite_if_older;
+  gchar* symlink_as;
   gboolean use_time_recvd;
 } AFFileDestDriver;
 
@@ -56,6 +57,7 @@
 void affile_dd_set_create_dirs(LogDriver *s, gboolean create_dirs);
 void affile_dd_set_fsync(LogDriver *s, gboolean enable);
 void affile_dd_set_overwrite_if_older(LogDriver *s, gint overwrite_if_older);
+void affile_dd_set_symlink_as(LogDriver *s, const gchar *symlink_as);
 void affile_dd_set_local_time_zone(LogDriver *s, const gchar *local_time_zone);
 void affile_dd_set_time_reap(LogDriver *s, gint time_reap);
 void affile_dd_global_init(void);
--- a/modules/affile/affile-grammar.y	2021-07-06 17:57:35.000000000 +0100
+++ b/modules/affile/affile-grammar.y	2021-12-03 19:11:40.930625812 +0000
@@ -430,6 +430,7 @@
 %token KW_FSYNC
 %token KW_FOLLOW_FREQ
 %token KW_OVERWRITE_IF_OLDER
+%token KW_SYMLINK_AS
 %token KW_MULTI_LINE_MODE
 %token KW_MULTI_LINE_PREFIX
 %token KW_MULTI_LINE_GARBAGE
@@ -637,6 +638,7 @@
         | file_perm_option
 	| KW_OPTIONAL '(' yesno ')'		{ last_driver->optional = $3; }
 	| KW_OVERWRITE_IF_OLDER '(' nonnegative_integer ')'	{ affile_dd_set_overwrite_if_older(last_driver, $3); }
+	| KW_SYMLINK_AS '(' string ')'		{ affile_dd_set_symlink_as(last_driver, $3); }
 	| KW_FSYNC '(' yesno ')'		{ affile_dd_set_fsync(last_driver, $3); }
         | dest_affile_common_option
 	;
--- a/modules/affile/affile-parser.c	2021-07-06 17:55:58.000000000 +0100
+++ b/modules/affile/affile-parser.c	2021-12-03 19:11:40.931625812 +0000
@@ -47,6 +47,7 @@
   { "fsync",              KW_FSYNC },
   { "remove_if_older",    KW_OVERWRITE_IF_OLDER, KWS_OBSOLETE, "overwrite_if_older" },
   { "overwrite_if_older", KW_OVERWRITE_IF_OLDER },
+  { "symlink_as",         KW_SYMLINK_AS },
   { "follow_freq",        KW_FOLLOW_FREQ },
   { "multi_line_mode",    KW_MULTI_LINE_MODE  },
   { "multi_line_prefix",  KW_MULTI_LINE_PREFIX },
--- a/modules/affile/file-opener.c	2021-07-06 17:55:58.000000000 +0100
+++ b/modules/affile/file-opener.c	2021-12-04 13:08:20.879075821 +0000
@@ -183,6 +183,86 @@
 }
 
 void
+file_opener_symlink(FileOpener *self, const gchar *name, const gchar *target)
+{
+  cap_t saved_caps;
+  gboolean do_symlink = FALSE;
+
+  msg_trace("file_opener_symlink",
+            evt_tag_str("filename", name),
+            evt_tag_str("target", target));
+
+  /* do not proceed if name already exists and is not a symlink; also, do not
+   * disturb the symlink unnecessarily (avoids annoying a running "tail -f",
+   * etc.) */
+
+  size_t n = strlen(target);
+  if (n > SSIZE_MAX - 1) return; /* avoid overflowing ssize_t */
+  gchar *s = malloc(n + 1);
+  if (s == NULL) return;
+  ssize_t i = readlink(name, s, n + 1);
+
+  if (i == -1)
+    {
+      if (errno == ENOENT)
+        /* symlink doesn't exist: create it */
+        do_symlink = TRUE;
+      else
+        /* not a symlink: leave it alone */
+        msg_error("Error reading symlink",
+                  evt_tag_str("filename", name),
+                  evt_tag_errno(EVT_TAG_OSERROR, errno));
+    }
+  else
+    {
+      /* readlink() does not append a null terminator, so if i == n + 1,
+       * truncation may have occurred; in any case i != n implies s != target,
+       * which is all we're interested in */
+      if ((i != n) || (strncmp(s, target, n) != 0))
+        {
+          /* symlink to something else: replace it */
+          if (unlink(name) == -1)
+            msg_error("Error removing symlink",
+                      evt_tag_str("filename", name),
+                      evt_tag_errno(EVT_TAG_OSERROR, errno));
+          else
+            do_symlink = TRUE;
+        }
+    }
+  free(s);
+
+  if (!do_symlink) return;
+
+  saved_caps = g_process_cap_save();
+
+  if (!_obtain_capabilities(self, name, &saved_caps))
+    {
+      g_process_cap_restore(saved_caps);
+      return;
+    }
+
+  g_process_enable_cap("cap_chown");
+
+  msg_info("Creating symlink",
+           evt_tag_str("filename", name),
+           evt_tag_str("target", target));
+
+  i = symlink(target, name);
+  if (i == -1)
+    msg_error("Error creating symlink",
+              evt_tag_str("filename", name),
+              evt_tag_str("target", target),
+              evt_tag_errno(EVT_TAG_OSERROR, errno));
+  else if (!file_perm_options_apply_symlink(&self->options->file_perm_options, name))
+    msg_error("Error setting symlink ownership",
+              evt_tag_str("filename", name),
+              evt_tag_errno(EVT_TAG_OSERROR, errno));
+
+  g_process_cap_restore(saved_caps);
+
+}
+
+void
 file_opener_set_options(FileOpener *self, FileOpenerOptions *options)
 {
   self->options = options;
--- a/modules/affile/file-opener.h	2021-07-06 17:55:58.000000000 +0100
+++ b/modules/affile/file-opener.h	2021-12-04 12:10:56.942891895 +0000
@@ -87,6 +87,8 @@
 
 FileOpenerResult file_opener_open_fd(FileOpener *self, const gchar *name, FileDirection dir, gint *fd);
 
+void file_opener_symlink(FileOpener *self, const gchar *name, const gchar *target);
+
 void file_opener_set_options(FileOpener *self, FileOpenerOptions *options);
 void file_opener_init_instance(FileOpener *self);
 FileOpener *file_opener_new(void);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.