diff --git a/cliutils/daemonize.nim b/cliutils/daemonize.nim index 21cd1f0..a638d8f 100644 --- a/cliutils/daemonize.nim +++ b/cliutils/daemonize.nim @@ -19,27 +19,50 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid = if fileExists(pidfile): raise newException(IOError, "pidfile " & pidfile & " already exists, daemon already running?") + # We're about to do a little dance to end up with a process that is + # disconnected from the calling program's process group and is not a session + # leader (a daemon process). + # + # Remember, after a call to fork the code is now running in two processes, + # the parent process and the child process. So the returned PID will be a + # positive value if we are still on the parent process, and represents the + # PID of the newly created process. If we are on the newly created process, + # the PID returned with be 0. + let pid1 = fork() # Are we the child process? if pid1 == 0: + # Yes, so let's get ready to execute the main code given to us. Note that + # this child process will not actually live long. As part of our setup, + # we're going to create a new process session, resulting in this child + # process being the process group owner and session leader. We don't our + # daemon process to be a session leader because that allows it to be + # connected to a terminal. So after we've setup the new process group we + # will fork again and actually run the logic we're daemonizing in the + # grandchild process. - # Yes, so let's get ready to execute the main code given to us. + # Ignore whatever the original working directory was. discard chdir("/") + + # Create a new session to decouple us from the original terminal. discard setsid() + + # Set the process file mode creation mask to 0000 discard umask(0) - # Fork again... but I'm not sure why. + # Fork again so the grandchild process will not be the session leader. let pid2 = fork() - # We don't need the intermediate process, so if we are not the child this - # time, lets just quit + # If we're still on the child process, we're done. if pid2 > 0: quit(QuitSuccess) - # If we are the grandchild let's set up our environment. + # Otherwise, if we are the grandchild let's finish setting up our + # environment. flushFile(stdout) flushFile(stderr) + # Setup our STDIN, STDERR, and STDOUT pipes (if given) if si.len > 0: fi = open(si, fmRead) discard dup2(getFileHandle(fi), getFileHandle(stdin)) @@ -52,17 +75,20 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid = fe = open(se, fmAppend) discard dup2(getFileHandle(fe), getFileHandle(stderr)) + # Keep a record of the PID filename in our process memory so we can + # reference it in our cleanup hooks. pidFileInner = pidfile # Add hooks to cleanup after ourselves when we're asked to die. signal(SIGINT, onStop) signal(SIGTERM, onStop) - # Find out what our actual PID is and save it + # Find out what our actual PID is and save it to the PID file. let childPid = getpid() writeFile(pidfile, $childPid) - # Finally, execute our main + # Finally, execute the logic given to us to daemonize daemonMain() + # If we're not the child proces, return the new PID of the daemonized process return pid1