]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
Use a lock to not fail on a left-over pid file.
authorWayne Davison <wayne@opencoder.net>
Fri, 5 Jun 2020 00:55:20 +0000 (17:55 -0700)
committerWayne Davison <wayne@opencoder.net>
Fri, 5 Jun 2020 02:08:03 +0000 (19:08 -0700)
clientserver.c
rsyncd.conf.yo
socket.c

index 3af97d84516b8435f73e4a1d333b1c82f4aadb4b..d56f5d52ffa6ba59709589c3a508b1902f40cacc 100644 (file)
@@ -66,6 +66,7 @@ extern gid_t our_gid;
 char *auth_user;
 int read_only = 0;
 int module_id = -1;
+int pid_file_fd = -1;
 struct chmod_mode_struct *daemon_chmod_modes;
 
 /* module_dirlen is the length of the module_dir string when in daemon
@@ -1149,26 +1150,59 @@ int start_daemon(int f_in, int f_out)
 static void create_pid_file(void)
 {
        char *pid_file = lp_pid_file();
-       char pidbuf[16];
-       pid_t pid = getpid();
-       int fd, len;
+       char pidbuf[32];
+       STRUCT_STAT st1, st2;
+       char *fail = NULL;
 
        if (!pid_file || !*pid_file)
                return;
 
-       cleanup_set_pid(pid);
-       if ((fd = do_open(pid_file, O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) {
-         failure:
-               cleanup_set_pid(0);
-               fprintf(stderr, "failed to create pid file %s: %s\n", pid_file, strerror(errno));
-               rsyserr(FLOG, errno, "failed to create pid file %s", pid_file);
+       /* These tests make sure that a temp-style lock dir is handled safely. */
+       st1.st_mode = 0;
+       if (do_lstat(pid_file, &st1) == 0 && !S_ISREG(st1.st_mode) && unlink(pid_file) < 0)
+               fail = "unlink";
+       else if ((pid_file_fd = do_open(pid_file, O_RDWR|O_CREAT, 0664)) < 0)
+               fail = S_ISREG(st1.st_mode) ? "open" : "create";
+       else if (!lock_range(pid_file_fd, 0, 4))
+               fail = "lock";
+       else if (do_fstat(pid_file_fd, &st1) < 0)
+               fail = "fstat opened";
+       else if (st1.st_size >= (int)sizeof pidbuf)
+               fail = "find small";
+       else if (do_lstat(pid_file, &st2) < 0)
+               fail = "lstat";
+       else if (!S_ISREG(st1.st_mode))
+               fail = "avoid file overwrite race for";
+       else if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
+               fail = "verify stat info for";
+#ifdef HAVE_FTRUNCATE
+       else if (do_ftruncate(pid_file_fd, 0) < 0)
+               fail = "truncate";
+#endif
+       else {
+               pid_t pid = getpid();
+               int len = snprintf(pidbuf, sizeof pidbuf, "%d\n", (int)pid);
+#ifndef HAVE_FTRUNCATE
+               /* What can we do with a too-long file and no truncate? I guess we'll add extra newlines. */
+               while (len < st1.st_size) /* We already verfified that size+1 chars fits in the buffer. */
+                       pidbuf[len++] = '\n';
+               /* We don't need the buffer to end in a '\0' (and we may not have room to add it). */
+#endif
+               if (write(pid_file_fd, pidbuf, len) != len)
+                        fail = "write";
+               cleanup_set_pid(pid); /* Mark the file for removal on exit, even if the write failed. */
+       }
+
+       if (fail) {
+               char msg[1024];
+               snprintf(msg, sizeof msg, "failed to %s pid file %s: %s\n",
+                       fail, pid_file, strerror(errno));
+               fputs(msg, stderr);
+               rprintf(FLOG, "%s", msg);
                exit_cleanup(RERR_FILEIO);
        }
-       snprintf(pidbuf, sizeof pidbuf, "%d\n", (int)pid);
-       len = strlen(pidbuf);
-       if (write(fd, pidbuf, len) != len)
-               goto failure;
-       close(fd);
+
+       /* The file is left open so that the lock remains valid. It is closed in our forked child procs. */
 }
 
 /* Become a daemon, discarding the controlling terminal. */
index aac4a7f2ea437009aaa2fa7dbd89bf7a8974c0c6..c8338664f4c9885bd642c923fc912768bee0f378 100644 (file)
@@ -103,9 +103,10 @@ This can be overridden by the bf(--dparam=motdfile=FILE)
 command-line option when starting the daemon.
 
 dit(bf(pid file)) This parameter tells the rsync daemon to write
-its process ID to that file.  If the file already exists, the rsync
-daemon will abort rather than overwrite the file.
-This can be overridden by the bf(--dparam=pidfile=FILE)
+its process ID to that file.  The rsync keeps the file locked so that
+it can know when it is safe to overwrite an existing file.
+
+The filename can be overridden by the bf(--dparam=pidfile=FILE)
 command-line option when starting the daemon.
 
 dit(bf(port)) You can override the default port the daemon will listen on
index 70fb16953524e0c02e20e5f47cf6b3125174758a..11ab4a83a4f2babf9bb4a11b998772c20a2f73b5 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -38,6 +38,7 @@ extern char *bind_address;
 extern char *sockopts;
 extern int default_af_hint;
 extern int connect_timeout;
+extern int pid_file_fd;
 
 #ifdef HAVE_SIGACTION
 static struct sigaction sigact;
@@ -609,6 +610,8 @@ void start_accept_loop(int port, int (*fn)(int, int))
 
                if ((pid = fork()) == 0) {
                        int ret;
+                       if (pid_file_fd >= 0)
+                               close(pid_file_fd);
                        for (i = 0; sp[i] >= 0; i++)
                                close(sp[i]);
                        /* Re-open log file in child before possibly giving