diff options
Diffstat (limited to 'src/global/pidfile.cc')
-rw-r--r-- | src/global/pidfile.cc | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/src/global/pidfile.cc b/src/global/pidfile.cc new file mode 100644 index 000000000..6a6b939f1 --- /dev/null +++ b/src/global/pidfile.cc @@ -0,0 +1,250 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/debug.h" +#include "common/errno.h" +#include "common/safe_io.h" +#include "global/pidfile.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#if defined(__FreeBSD__) +#include <sys/param.h> +#endif + +#include "include/compat.h" + +using std::string; + +// +// derr can be used for functions exclusively called from pidfile_write +// +// cerr must be used for functions called by pidfile_remove because +// logging is not functional when it is called. cerr output is lost +// when the caller is daemonized but it will show if not (-f) +// +#define dout_context g_ceph_context +#define dout_prefix *_dout +#define dout_subsys ceph_subsys_ + +struct pidfh { + int pf_fd; + string pf_path; + dev_t pf_dev; + ino_t pf_ino; + + pidfh() { + reset(); + } + ~pidfh() { + remove(); + } + + bool is_open() const { + return !pf_path.empty() && pf_fd != -1; + } + void reset() { + pf_fd = -1; + pf_path.clear(); + pf_dev = 0; + pf_ino = 0; + } + int verify(); + int remove(); + int open(std::string_view pid_file); + int write(); +}; + +static pidfh *pfh = nullptr; + +int pidfh::verify() { + // check that the file we opened still is the same + if (pf_fd == -1) + return -EINVAL; + struct stat st; + if (stat(pf_path.c_str(), &st) == -1) + return -errno; + if (st.st_dev != pf_dev || st.st_ino != pf_ino) + return -ESTALE; + return 0; +} + +int pidfh::remove() +{ + if (pf_path.empty()) + return 0; + + int ret; + if ((ret = verify()) < 0) { + if (pf_fd != -1) { + ::close(pf_fd); + reset(); + } + return ret; + } + + // seek to the beginning of the file before reading + ret = ::lseek(pf_fd, 0, SEEK_SET); + if (ret < 0) { + std::cerr << __func__ << " lseek failed " + << cpp_strerror(errno) << std::endl; + return -errno; + } + + // check that the pid file still has our pid in it + char buf[32]; + memset(buf, 0, sizeof(buf)); + ssize_t res = safe_read(pf_fd, buf, sizeof(buf)); + ::close(pf_fd); + if (res < 0) { + std::cerr << __func__ << " safe_read failed " + << cpp_strerror(-res) << std::endl; + return res; + } + + int a = atoi(buf); + if (a != getpid()) { + std::cerr << __func__ << " the pid found in the file is " + << a << " which is different from getpid() " + << getpid() << std::endl; + return -EDOM; + } + ret = ::unlink(pf_path.c_str()); + if (ret < 0) { + std::cerr << __func__ << " unlink " << pf_path.c_str() << " failed " + << cpp_strerror(errno) << std::endl; + return -errno; + } + reset(); + return 0; +} + +int pidfh::open(std::string_view pid_file) +{ + pf_path = pid_file; + + int fd; + fd = ::open(pf_path.c_str(), O_CREAT|O_RDWR|O_CLOEXEC, 0644); + if (fd < 0) { + int err = errno; + derr << __func__ << ": failed to open pid file '" + << pf_path << "': " << cpp_strerror(err) << dendl; + reset(); + return -err; + } + struct stat st; + if (fstat(fd, &st) == -1) { + int err = errno; + derr << __func__ << ": failed to fstat pid file '" + << pf_path << "': " << cpp_strerror(err) << dendl; + ::close(fd); + reset(); + return -err; + } + + pf_fd = fd; + pf_dev = st.st_dev; + pf_ino = st.st_ino; + + // Default Windows file share flags prevent other processes from writing + // to this file. + #ifndef _WIN32 + struct flock l = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + int r = ::fcntl(pf_fd, F_SETLK, &l); + if (r < 0) { + if (errno == EAGAIN || errno == EACCES) { + derr << __func__ << ": failed to lock pidfile " + << pf_path << " because another process locked it" + << "': " << cpp_strerror(errno) << dendl; + } else { + derr << __func__ << ": failed to lock pidfile " + << pf_path << "': " << cpp_strerror(errno) << dendl; + } + const auto lock_errno = errno; + ::close(pf_fd); + reset(); + return -lock_errno; + } + #endif + return 0; +} + +int pidfh::write() +{ + if (!is_open()) + return 0; + + char buf[32]; + int len = snprintf(buf, sizeof(buf), "%d\n", getpid()); + if (::ftruncate(pf_fd, 0) < 0) { + int err = errno; + derr << __func__ << ": failed to ftruncate the pid file '" + << pf_path << "': " << cpp_strerror(err) << dendl; + return -err; + } + ssize_t res = safe_write(pf_fd, buf, len); + if (res < 0) { + derr << __func__ << ": failed to write to pid file '" + << pf_path << "': " << cpp_strerror(-res) << dendl; + return res; + } + return 0; +} + +void pidfile_remove() +{ + if (pfh != nullptr) + delete pfh; + pfh = nullptr; +} + +int pidfile_write(std::string_view pid_file) +{ + if (pid_file.empty()) { + dout(0) << __func__ << ": ignore empty --pid-file" << dendl; + return 0; + } + + ceph_assert(pfh == nullptr); + + pfh = new pidfh(); + if (atexit(pidfile_remove)) { + derr << __func__ << ": failed to set pidfile_remove function " + << "to run at exit." << dendl; + return -EINVAL; + } + + int r = pfh->open(pid_file); + if (r != 0) { + pidfile_remove(); + return r; + } + + r = pfh->write(); + if (r != 0) { + pidfile_remove(); + return r; + } + + return 0; +} |