ctdb-common: Add routines to manage PID file
authorAmitay Isaacs <amitay@gmail.com>
Mon, 19 Sep 2016 06:30:12 +0000 (16:30 +1000)
committerAmitay Isaacs <amitay@samba.org>
Thu, 22 Sep 2016 06:34:20 +0000 (08:34 +0200)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=12287

Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
ctdb/common/pidfile.c [new file with mode: 0644]
ctdb/common/pidfile.h [new file with mode: 0644]
ctdb/tests/cunit/pidfile_test_001.sh [new file with mode: 0755]
ctdb/tests/src/pidfile_test.c [new file with mode: 0644]
ctdb/wscript

diff --git a/ctdb/common/pidfile.c b/ctdb/common/pidfile.c
new file mode 100644 (file)
index 0000000..b3f29e3
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+   Create and remove pidfile
+
+   Copyright (C) Amitay Isaacs  2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <talloc.h>
+
+#include "common/pidfile.h"
+
+struct pidfile_context {
+       const char *pidfile;
+       int fd;
+       pid_t pid;
+};
+
+static int pidfile_context_destructor(struct pidfile_context *pid_ctx);
+
+int pidfile_create(TALLOC_CTX *mem_ctx, const char *pidfile,
+                  struct pidfile_context **result)
+{
+       struct pidfile_context *pid_ctx;
+       struct flock lck;
+       char tmp[64];
+       int fd, ret = 0;
+       int len;
+       ssize_t nwritten;
+
+       pid_ctx = talloc_zero(mem_ctx, struct pidfile_context);
+       if (pid_ctx == NULL) {
+               return ENOMEM;
+       }
+
+       pid_ctx->pidfile = talloc_strdup(pid_ctx, pidfile);
+       if (pid_ctx->pidfile == NULL) {
+               ret = ENOMEM;
+               goto fail;
+       }
+
+       pid_ctx->pid = getpid();
+
+       fd = open(pidfile, O_CREAT|O_WRONLY|O_NONBLOCK, 0644);
+       if (fd == -1) {
+               ret = errno;
+               goto fail;
+       }
+
+       pid_ctx->fd = fd;
+
+       lck = (struct flock) {
+               .l_type = F_WRLCK,
+               .l_whence = SEEK_SET,
+       };
+
+       do {
+               ret = fcntl(fd, F_SETLK, &lck);
+       } while ((ret == -1) && (errno == EINTR));
+
+       if (ret != 0) {
+               ret = errno;
+               goto fail;
+       }
+
+       do {
+               ret = ftruncate(fd, 0);
+       } while ((ret == -1) && (errno == EINTR));
+
+       if (ret == -1) {
+               ret = EIO;
+               goto fail_unlink;
+       }
+
+       len = snprintf(tmp, sizeof(tmp), "%u\n", pid_ctx->pid);
+       if (len < 0) {
+               ret = EIO;
+               goto fail_unlink;
+       }
+
+       do {
+               nwritten = write(fd, tmp, len);
+       } while ((nwritten == -1) && (errno == EINTR));
+
+       if ((nwritten == -1) || (nwritten != len)) {
+               ret = EIO;
+               goto fail_unlink;
+       }
+
+       talloc_set_destructor(pid_ctx, pidfile_context_destructor);
+
+       *result = pid_ctx;
+       return 0;
+
+fail_unlink:
+       unlink(pidfile);
+       close(fd);
+
+fail:
+       talloc_free(pid_ctx);
+       return ret;
+}
+
+static int pidfile_context_destructor(struct pidfile_context *pid_ctx)
+{
+       struct flock lck;
+       int ret;
+
+       if (getpid() != pid_ctx->pid) {
+               return 0;
+       }
+
+       lck = (struct flock) {
+               .l_type = F_UNLCK,
+               .l_whence = SEEK_SET,
+       };
+
+       (void) unlink(pid_ctx->pidfile);
+
+       do {
+               ret = fcntl(pid_ctx->fd, F_SETLK, &lck);
+       } while ((ret == -1) && (errno == EINTR));
+
+       do {
+               ret = close(pid_ctx->fd);
+       } while ((ret == -1) && (errno == EINTR));
+
+       return 0;
+}
diff --git a/ctdb/common/pidfile.h b/ctdb/common/pidfile.h
new file mode 100644 (file)
index 0000000..1450134
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+   Create and remove pidfile
+
+   Copyright (C) Amitay Isaacs  2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __CTDB_PIDFILE_H__
+#define __CTDB_PIDFILE_H__
+
+#include <talloc.h>
+
+/**
+ * @file pidfile.h
+ *
+ * @brief Routines to manage PID file
+ */
+
+/**
+ * @brief Abstract struct to store pidfile details
+ */
+struct pidfile_context;
+
+/**
+ * @brief Create a PID file
+ *
+ * This creates a PID file, locks it, and writes PID.
+ *
+ * @param[in] mem_ctx Talloc memory context
+ * @param[in] pidfile Path of PID file
+ * @param[out] result Pidfile context
+ * @return 0 on success, errno on failure
+ *
+ * Freeing the pidfile_context, will delete the pidfile.
+ */
+int pidfile_create(TALLOC_CTX *mem_ctx, const char *pidfile,
+                  struct pidfile_context **result);
+
+#endif /* __CTDB_PIDFILE_H__ */
diff --git a/ctdb/tests/cunit/pidfile_test_001.sh b/ctdb/tests/cunit/pidfile_test_001.sh
new file mode 100755 (executable)
index 0000000..620682e
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+. "${TEST_SCRIPTS_DIR}/unit.sh"
+
+pidfile=$(mktemp --tmpdir="$TEST_VAR_DIR")
+
+ok_null
+unit_test pidfile_test $pidfile
diff --git a/ctdb/tests/src/pidfile_test.c b/ctdb/tests/src/pidfile_test.c
new file mode 100644 (file)
index 0000000..ad8bf14
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+   pidfile tests
+
+   Copyright (C) Amitay Isaacs  2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/wait.h"
+
+#include <assert.h>
+
+#include "common/pidfile.c"
+
+
+/* create pid file, check pid file exists, check pid and remove pid file */
+static void test1(const char *pidfile)
+{
+       struct pidfile_context *pid_ctx;
+       int ret;
+       struct stat st;
+       FILE *fp;
+       pid_t pid;
+
+       ret = pidfile_create(NULL, pidfile, &pid_ctx);
+       assert(ret == 0);
+       assert(pid_ctx != NULL);
+
+       ret = stat(pidfile, &st);
+       assert(ret == 0);
+       assert(S_ISREG(st.st_mode));
+
+       fp = fopen(pidfile, "r");
+       ret = fscanf(fp, "%d", &pid);
+       assert(ret == 1);
+       assert(pid == getpid());
+       fclose(fp);
+
+       TALLOC_FREE(pid_ctx);
+
+       ret = stat(pidfile, &st);
+       assert(ret == -1);
+}
+
+/* create pid file in two processes */
+static void test2(const char *pidfile)
+{
+       struct pidfile_context *pid_ctx;
+       pid_t pid, pid2;
+       int fd[2];
+       int ret;
+       size_t nread;
+       FILE *fp;
+       struct stat st;
+
+       ret = pipe(fd);
+       assert(ret == 0);
+
+       pid = fork();
+       assert(pid != -1);
+
+       if (pid == 0) {
+               ssize_t nwritten;
+
+               close(fd[0]);
+
+               ret = pidfile_create(NULL, pidfile, &pid_ctx);
+               assert(ret == 0);
+               assert(pid_ctx != NULL);
+
+               nwritten = write(fd[1], &ret, sizeof(ret));
+               assert(nwritten == sizeof(ret));
+
+               sleep(10);
+
+               TALLOC_FREE(pid_ctx);
+
+               nwritten = write(fd[1], &ret, sizeof(ret));
+               assert(nwritten == sizeof(ret));
+
+               exit(1);
+       }
+
+       close(fd[1]);
+
+       nread = read(fd[0], &ret, sizeof(ret));
+       assert(nread == sizeof(ret));
+       assert(ret == 0);
+
+       fp = fopen(pidfile, "r");
+       assert(fp != NULL);
+       ret = fscanf(fp, "%d", &pid2);
+       assert(ret == 1);
+       assert(pid == pid2);
+       fclose(fp);
+
+       ret = pidfile_create(NULL, pidfile, &pid_ctx);
+       assert(ret != 0);
+
+       nread = read(fd[0], &ret, sizeof(ret));
+       assert(nread == sizeof(ret));
+       assert(ret == 0);
+
+       ret = pidfile_create(NULL, pidfile, &pid_ctx);
+       assert(ret == 0);
+       assert(pid_ctx != NULL);
+
+       TALLOC_FREE(pid_ctx);
+
+       ret = stat(pidfile, &st);
+       assert(ret == -1);
+}
+
+/* create pid file, fork, try to remove pid file in separate process */
+static void test3(const char *pidfile)
+{
+       struct pidfile_context *pid_ctx;
+       pid_t pid;
+       int fd[2];
+       int ret;
+       size_t nread;
+       struct stat st;
+
+       ret = pidfile_create(NULL, pidfile, &pid_ctx);
+       assert(ret == 0);
+       assert(pid_ctx != NULL);
+
+       ret = pipe(fd);
+       assert(ret == 0);
+
+       pid = fork();
+       assert(pid != -1);
+
+       if (pid == 0) {
+               ssize_t nwritten;
+
+               close(fd[0]);
+
+               TALLOC_FREE(pid_ctx);
+
+               nwritten = write(fd[1], &ret, sizeof(ret));
+               assert(nwritten == sizeof(ret));
+
+               exit(1);
+       }
+
+       close(fd[1]);
+
+       nread = read(fd[0], &ret, sizeof(ret));
+       assert(nread == sizeof(ret));
+
+       ret = stat(pidfile, &st);
+       assert(ret == 0);
+
+       TALLOC_FREE(pid_ctx);
+
+       ret = stat(pidfile, &st);
+       assert(ret == -1);
+}
+
+/* create pid file, kill process, overwrite pid file in different process */
+static void test4(const char *pidfile)
+{
+       struct pidfile_context *pid_ctx;
+       pid_t pid, pid2;
+       int fd[2];
+       int ret;
+       size_t nread;
+       struct stat st;
+
+       ret = pipe(fd);
+       assert(ret == 0);
+
+       pid = fork();
+       assert(pid != -1);
+
+       if (pid == 0) {
+               ssize_t nwritten;
+
+               close(fd[0]);
+
+               ret = pidfile_create(NULL, pidfile, &pid_ctx);
+
+               nwritten = write(fd[1], &ret, sizeof(ret));
+               assert(nwritten == sizeof(ret));
+
+               sleep(99);
+               exit(1);
+       }
+
+       close(fd[1]);
+
+       nread = read(fd[0], &ret, sizeof(ret));
+       assert(nread == sizeof(ret));
+       assert(ret == 0);
+
+       ret = stat(pidfile, &st);
+       assert(ret == 0);
+
+       ret = kill(pid, SIGKILL);
+       assert(ret == 0);
+
+       pid2 = waitpid(pid, &ret, 0);
+       assert(pid2 == pid);
+
+       ret = pidfile_create(NULL, pidfile, &pid_ctx);
+       assert(ret == 0);
+       assert(pid_ctx != NULL);
+
+       ret = stat(pidfile, &st);
+       assert(ret == 0);
+
+       TALLOC_FREE(pid_ctx);
+}
+
+int main(int argc, const char **argv)
+{
+       if (argc != 2) {
+               fprintf(stderr, "Usage: %s <pidfile>\n", argv[0]);
+               exit(1);
+       }
+
+       test1(argv[1]);
+       test2(argv[1]);
+       test3(argv[1]);
+       test4(argv[1]);
+
+       return 0;
+}
index 1e80e017e3834eb93a34ef041a2b49428acd039a..56758495a0b838757607dbff6b88c9bab7b0b38a 100644 (file)
@@ -346,7 +346,8 @@ def build(bld):
                         source=bld.SUBDIR('common',
                                           '''db_hash.c srvid.c reqid.c
                                              pkt_read.c pkt_write.c comm.c
-                                             logging.c rb_tree.c tunable.c'''),
+                                             logging.c rb_tree.c tunable.c
+                                             pidfile.c'''),
                         deps='''samba-util tevent-util
                                 replace talloc tevent tdb''')
 
@@ -657,6 +658,7 @@ def build(bld):
         'comm_client_test',
         'protocol_types_test',
         'protocol_client_test',
+        'pidfile_test',
     ]
 
     for target in ctdb_unit_tests: