1"""Disk And Execution MONitor (Daemon) 2 3Configurable daemon behaviors: 4 5 1.) The current working directory set to the / directory. 6 2.) The current file creation mode mask set to 0. 7 3.) Close all open files (1024). 8 4.) Redirect standard I/O streams to /dev/null. 9 10A failed call to fork() now raises an exception. 11 12References: 13 1) Advanced Programming in the Unix Environment: W. Richard Stevens 14 2) Unix Programming Frequently Asked Questions: http://www.erlenstar.demon.co.uk/unix/faq_toc.html 15""" 16 17__author__ = "Chad J. Schroeder" 18__copyright__ = "Copyright (C) 2005 Chad J. Schroeder" 19__revision__ = "$Id$" 20__version__ = "0.2" 21 22# Standard Python modules. 23import os # Miscellaneous OS interfaces. 24import sys # System-specific parameters and functions. 25 26# Default daemon parameters. 27# File mode creation mask of the daemon. 28UMASK = 0 29 30# Default working directory for the daemon. 31WORKDIR = "/" 32 33# Default maximum for the number of available file descriptors. 34MAXFD = 1024 35 36# The standard I/O file descriptors are redirected to /dev/null by default. 37if (hasattr(os, "devnull")): 38 REDIRECT_TO = os.devnull 39else: 40 REDIRECT_TO = "/dev/null" 41 42def createDaemon(workDir = None): 43 """Detach a process from the controlling terminal and run it in the 44 background as a daemon. 45 """ 46 47 if not workDir is None and os.path.isdir(workDir): 48 global WORKDIR 49 WORKDIR = workDir 50 try: 51 # Fork a child process so the parent can exit. This returns control to 52 # the command-line or shell. It also guarantees that the child will not 53 # be a process group leader, since the child receives a new process ID 54 # and inherits the parent's process group ID. This step is required 55 # to insure that the next call to os.setsid is successful. 56 pid = os.fork() 57 except OSError as e: 58 raise Exception("%s [%d]" % (e.strerror, e.errno)) 59 60 if (pid == 0): # The first child. 61 # To become the session leader of this new session and the process group 62 # leader of the new process group, we call os.setsid(). The process is 63 # also guaranteed not to have a controlling terminal. 64 os.setsid() 65 66 # Is ignoring SIGHUP necessary? 67 # 68 # It's often suggested that the SIGHUP signal should be ignored before 69 # the second fork to avoid premature termination of the process. The 70 # reason is that when the first child terminates, all processes, e.g. 71 # the second child, in the orphaned group will be sent a SIGHUP. 72 # 73 # "However, as part of the session management system, there are exactly 74 # two cases where SIGHUP is sent on the death of a process: 75 # 76 # 1) When the process that dies is the session leader of a session that 77 # is attached to a terminal device, SIGHUP is sent to all processes 78 # in the foreground process group of that terminal device. 79 # 2) When the death of a process causes a process group to become 80 # orphaned, and one or more processes in the orphaned group are 81 # stopped, then SIGHUP and SIGCONT are sent to all members of the 82 # orphaned group." [2] 83 # 84 # The first case can be ignored since the child is guaranteed not to have 85 # a controlling terminal. The second case isn't so easy to dismiss. 86 # The process group is orphaned when the first child terminates and 87 # POSIX.1 requires that every STOPPED process in an orphaned process 88 # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the 89 # second child is not STOPPED though, we can safely forego ignoring the 90 # SIGHUP signal. In any case, there are no ill-effects if it is ignored. 91 # 92 # import signal # Set handlers for asynchronous events. 93 # signal.signal(signal.SIGHUP, signal.SIG_IGN) 94 95 try: 96 # Fork a second child and exit immediately to prevent zombies. This 97 # causes the second child process to be orphaned, making the init 98 # process responsible for its cleanup. And, since the first child is 99 # a session leader without a controlling terminal, it's possible for 100 # it to acquire one by opening a terminal in the future (System V- 101 # based systems). This second fork guarantees that the child is no 102 # longer a session leader, preventing the daemon from ever acquiring 103 # a controlling terminal. 104 pid = os.fork() # Fork a second child. 105 except OSError as e: 106 raise Exception("%s [%d]" % (e.strerror, e.errno)) 107 108 if (pid == 0): # The second child. 109 # Since the current working directory may be a mounted filesystem, we 110 # avoid the issue of not being able to unmount the filesystem at 111 # shutdown time by changing it to the root directory. 112 os.chdir(WORKDIR) 113 # We probably don't want the file mode creation mask inherited from 114 # the parent, so we give the child complete control over permissions. 115 os.umask(UMASK) 116 else: 117 # exit() or _exit()? See below. 118 os._exit(0) # Exit parent (the first child) of the second child. 119 else: 120 # exit() or _exit()? 121 # _exit is like exit(), but it doesn't call any functions registered 122 # with atexit (and on_exit) or any registered signal handlers. It also 123 # closes any open file descriptors. Using exit() may cause all stdio 124 # streams to be flushed twice and any temporary files may be unexpectedly 125 # removed. It's therefore recommended that child branches of a fork() 126 # and the parent branch(es) of a daemon use _exit(). 127 os._exit(0) # Exit parent of the first child. 128 129 # Close all open file descriptors. This prevents the child from keeping 130 # open any file descriptors inherited from the parent. There is a variety 131 # of methods to accomplish this task. Three are listed below. 132 # 133 # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum 134 # number of open file descriptors to close. If it doesn't exists, use 135 # the default value (configurable). 136 # 137 # try: 138 # maxfd = os.sysconf("SC_OPEN_MAX") 139 # except (AttributeError, ValueError): 140 # maxfd = MAXFD 141 # 142 # OR 143 # 144 # if (os.sysconf_names.has_key("SC_OPEN_MAX")): 145 # maxfd = os.sysconf("SC_OPEN_MAX") 146 # else: 147 # maxfd = MAXFD 148 # 149 # OR 150 # 151 # Use the getrlimit method to retrieve the maximum file descriptor number 152 # that can be opened by this process. If there is not limit on the 153 # resource, use the default value. 154 # 155 import resource # Resource usage information. 156 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] 157 if (maxfd == resource.RLIM_INFINITY): 158 maxfd = MAXFD 159 160 # Iterate through and close all file descriptors. 161 for fd in range(0, maxfd): 162 try: 163 os.close(fd) 164 except OSError: # ERROR, fd wasn't open to begin with (ignored) 165 pass 166 167 # Redirect the standard I/O file descriptors to the specified file. Since 168 # the daemon has no controlling terminal, most daemons redirect stdin, 169 # stdout, and stderr to /dev/null. This is done to prevent side-effects 170 # from reads and writes to the standard I/O file descriptors. 171 172 # This call to open is guaranteed to return the lowest file descriptor, 173 # which will be 0 (stdin), since it was closed above. 174 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) 175 176 # Duplicate standard input to standard output and standard error. 177 os.dup2(0, 1) # standard output (1) 178 os.dup2(0, 2) # standard error (2) 179 180 return(0) 181 182if __name__ == "__main__": 183 184 retCode = createDaemon('.') 185 186 # The code, as is, will create a new file in the root directory, when 187 # executed with superuser privileges. The file will contain the following 188 # daemon related process parameters: return code, process ID, parent 189 # process group ID, session ID, user ID, effective user ID, real group ID, 190 # and the effective group ID. Notice the relationship between the daemon's 191 # process ID, process group ID, and its parent's process ID. 192 193 procParams = """ 194 return code = %s 195 process ID = %s 196 parent process ID = %s 197 process group ID = %s 198 session ID = %s 199 user ID = %s 200 effective user ID = %s 201 real group ID = %s 202 effective group ID = %s 203 """ % (retCode, os.getpid(), os.getppid(), os.getpgrp(), os.getsid(0), 204 os.getuid(), os.geteuid(), os.getgid(), os.getegid()) 205 206 open("createDaemon.log", "w").write(procParams + "\n") 207 208 sys.exit(retCode) 209