-
Notifications
You must be signed in to change notification settings - Fork 57
chore!: drop daemonize crate #372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,16 +12,32 @@ | |
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| use std::{ffi::OsStr, os::unix::net::UnixStream, path::Path, process, thread, time::Duration}; | ||
| use std::{ | ||
| ffi::OsStr, | ||
| fs, | ||
| io::{Seek, SeekFrom, Write}, | ||
| os::unix::net::UnixStream, | ||
| path::{Path, PathBuf}, | ||
| process, thread, | ||
| time::Duration, | ||
| }; | ||
|
|
||
| use crate::{config, consts, Args}; | ||
|
|
||
| use anyhow::{anyhow, Context}; | ||
| use tracing::info; | ||
| use nix::{ | ||
| fcntl, | ||
| fcntl::FlockArg, | ||
| sys::{wait, wait::WaitStatus}, | ||
| unistd, | ||
| unistd::ForkResult, | ||
| }; | ||
| use tracing::{error, info}; | ||
|
|
||
| /// Check if we can connect to the control socket, and if we | ||
| /// can't, fork the daemon in the background. | ||
| pub fn maybe_fork_daemon<B, P>( | ||
| /// can't, launch the daemon in the background. This should be | ||
| /// called by client subcommands like `shpool attach` or `shpool list` | ||
| pub fn maybe_launch_daemon<B, P>( | ||
| config_manager: &config::Manager, | ||
| args: &Args, | ||
| shpool_bin: B, | ||
|
|
@@ -106,3 +122,132 @@ where | |
|
|
||
| Err(anyhow!("daemonizing: launched daemon, but control socket never came up")) | ||
| } | ||
|
|
||
| pub struct PidFileGuard { | ||
| p: PathBuf, | ||
| } | ||
|
|
||
| impl PidFileGuard { | ||
| pub fn path(&self) -> &PathBuf { | ||
| &self.p | ||
| } | ||
| } | ||
|
|
||
| impl std::ops::Drop for PidFileGuard { | ||
| fn drop(&mut self) { | ||
| if let Err(e) = std::fs::remove_file(&self.p) { | ||
| error!("cleaning up pid file: {:?}", e); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Perform the traditional daemonization double-fork setsid dance. | ||
| /// This should be called from within `shpool daemon` to detach it | ||
| /// from the launching shell. | ||
| /// | ||
| /// Safety: see nix::unistd::fork for preconditions. | ||
| pub unsafe fn daemonize(pid_path: PathBuf) -> anyhow::Result<PidFileGuard> { | ||
| let old_mask = nix::sys::stat::umask(nix::sys::stat::Mode::empty()); | ||
| info!("set empty umask (old mask: {:?}", old_mask); | ||
|
|
||
| // `cd /` in order to avoid holding open the directory the user | ||
| // happened to launch `shpool attach` in so that we don't block | ||
| // deletes of that directory the whole time that the daemon | ||
| // sticks around. | ||
| std::env::set_current_dir("/").context("cding to root")?; | ||
|
|
||
| // Fork and become the child in order to stop being the process group | ||
| // leader. We need to avoid being the process group leader in order | ||
| // to call setsid(). | ||
| // | ||
| // Safety: the caller has ensured fork preconditions are met, which | ||
| // meet the become_child preconditions. | ||
| unsafe { become_child(true) }.context("first fork")?; | ||
|
|
||
| let sid = unistd::setsid().context("creating new session")?; | ||
| info!("while daemonizing setsid() = {}", sid); | ||
|
|
||
| let pid_file = fs::OpenOptions::new() | ||
| .read(true) | ||
| .write(true) | ||
| .create(true) | ||
| .truncate(false) | ||
| .open(&pid_path) | ||
| .context("opening pid file")?; | ||
| let mut pid_file = match fcntl::Flock::lock(pid_file, FlockArg::LockExclusiveNonblock) { | ||
| Ok(l) => l, | ||
| Err((_, errno)) if errno == nix::errno::Errno::EWOULDBLOCK => { | ||
| return Err(anyhow!("another daemon is already running")); | ||
| } | ||
| Err((_, errno)) => { | ||
| return Err(anyhow!("locking pid file: {:?}", errno)); | ||
| } | ||
| }; | ||
|
|
||
| // Safety: the caller has ensured fork preconditions are met, which | ||
| // meet the become_child preconditions. | ||
| unsafe { become_child(false) }.context("second fork")?; | ||
|
|
||
| // Actually write the pid file contents only now that we are in | ||
| // the grandchild and have our final pid. | ||
| pid_file.set_len(0).context("truncating pid file")?; | ||
| pid_file.seek(SeekFrom::Start(0)).context("seeking to start of pid file")?; | ||
| writeln!(*pid_file, "{}", std::process::id()).context("writing pid")?; | ||
|
|
||
| redirect_std_fds_to_null()?; | ||
|
|
||
| Ok(PidFileGuard { p: pid_path }) | ||
| } | ||
|
|
||
| fn redirect_std_fds_to_null() -> anyhow::Result<()> { | ||
| // Safety: path is a valid null terminated string, O_RDWR is a valid flag. | ||
| let nullfd = unsafe { libc::open(b"/dev/null\0" as *const [u8; 10] as _, libc::O_RDWR) }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nix::fcntl::open? |
||
| if nullfd == -1 { | ||
| return Err(anyhow!("opening /dev/null: {}", nix::errno::Errno::last())); | ||
| } | ||
| // Safety: nullfd is valid and newfd need not be open for saftey | ||
| let fd = unsafe { libc::dup2(nullfd, libc::STDIN_FILENO) }; | ||
| if fd == -1 { | ||
| return Err(anyhow!("redirecting stdin to /dev/null: {}", nix::errno::Errno::last())); | ||
| } | ||
| // Safety: nullfd is valid and newfd need not be open for saftey | ||
| let fd = unsafe { libc::dup2(nullfd, libc::STDOUT_FILENO) }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should you be using nix's dup2 instead? https://docs.rs/nix/latest/nix/unistd/fn.dup2.html |
||
| if fd == -1 { | ||
| return Err(anyhow!("redirecting stdout to /dev/null: {}", nix::errno::Errno::last())); | ||
| } | ||
| // Safety: nullfd is valid and newfd need not be open for saftey | ||
| let fd = unsafe { libc::dup2(nullfd, libc::STDERR_FILENO) }; | ||
| if fd == -1 { | ||
| return Err(anyhow!("redirecting stderr to /dev/null: {}", nix::errno::Errno::last())); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// fork() and exit from the parent, so the only running process | ||
| /// is the child process. If `wait_child` is true, rather than | ||
| /// exiting immediately, the parent will wait for the child proc | ||
| /// to exit and exit with its exit code. In the double-fork dance, | ||
| /// we want to do this the first time so we propagate the error | ||
| /// in case of a crash in the intermediate proc. | ||
| /// | ||
| /// Safety: see nix::unistd::fork for preconditions. | ||
| unsafe fn become_child(wait_child: bool) -> anyhow::Result<()> { | ||
| // Safety: since the caller has me the fork preconditions, this is | ||
| // safe. | ||
| match unsafe { unistd::fork() }.context("forking for daemonization")? { | ||
| ForkResult::Parent { child } => { | ||
| if wait_child { | ||
| match wait::waitpid(child, None).context("waiting for child")? { | ||
| WaitStatus::Exited(_, status) => std::process::exit(status), | ||
| WaitStatus::Signaled(_, _, _) => std::process::exit(1), | ||
| _ => {} | ||
| } | ||
| } else { | ||
| std::process::exit(0); | ||
| } | ||
| } | ||
| ForkResult::Child => {} | ||
| } | ||
| Ok(()) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to close nullfd after we finish duping?