syslogd: work around rename() not renaming hardlinks to themselves
authorChristian Engelmayer <christian.engelmayer@frequentis.com>
Fri, 28 Oct 2011 16:12:42 +0000 (18:12 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 28 Oct 2011 16:12:42 +0000 (18:12 +0200)
Function log_locally() within the syslogd can potentially lock up when
restarting the daemon after a power loss in case the unplanned shutdown hit the
rename operation during logfile rotation.

While POSIX requires the rename operation to be atomic, many file systems such
as JFFS2 implement the rename operation in 2 steps by linking the new name
followed by unlinking the original name. In case of a power loss during the
rename the system can end up with /var/log/messages and /var/log/messages.0
being 2 hard links to the same file.

When the syslog daemon restarts in such a situation it will rediscover the need
to rotate the log files, however, POSIX also requires that rename does nothing
and reports success in case oldpath and newpath are existing hard links to the
same file. Looping through reopen: by (O_CREAT | O_APPEND), the daemon
eternally reopens the same file without succeeding to rotate.

Signed-off-by: Christian Engelmayer <christian.engelmayer@frequentis.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
sysklogd/syslogd.c

index d36d09cafae80082da9de112cec891f8560e2b8e..fc380d9f91bcfabc697489c9f6144b931cd02a5c 100644 (file)
@@ -594,6 +594,14 @@ static void log_locally(time_t now, char *msg, logFile_t *log_file)
                        }
                        /* newFile == "f.0" now */
                        rename(log_file->path, newFile);
+                       /* Incredibly, if F and F.0 are hardlinks, POSIX
+                        * _demands_ that rename returns 0 but does not
+                        * remove F!!!
+                        * (hardlinked F/F.0 pair was observed after
+                        * power failure during rename()).
+                        * Ensure old file is gone:
+                        */
+                       unlink(log_file->path);
 #ifdef SYSLOGD_WRLOCK
                        fl.l_type = F_UNLCK;
                        fcntl(log_file->fd, F_SETLKW, &fl);