This file is indexed.

/usr/lib/python3/dist-packages/cement/ext/ext_daemon.py is in python3-cement 2.10.0-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
"""
The Daemon Extension enables applications Built on Cement (tm) to
easily perform standard daemonization functions.

Requirements
------------

 * Python 2.6+, Python 3+
 * Available on Unix/Linux only

Features
--------

 * Configurable runtime user and group
 * Adds the ``--daemon`` command line option
 * Adds ``app.daemonize()`` function to trigger daemon functionality where
   necessary (either in a cement ``pre_run`` hook or an application controller
   sub-command, etc)
 * Manages a pid file including cleanup on ``app.close()``


Configuration
-------------

The daemon extension is configurable with the following settings under the
[daemon] section.

    * **user** - The user name to run the process as.
      Default: os.getlogin()
    * **group** - The group name to run the process as.
      Default: The primary group of the 'user'.
    * **dir** - The directory to run the process in.
      Default: /
    * **pid_file** - The filesystem path to store the PID (Process ID) file.
      Default: None
    * **umask** - The umask value to pass to os.umask().
      Default: 0


Configurations can be passed as defaults to a CementApp:

.. code-block:: python

    from cement.core.foundation import CementApp
    from cement.utils.misc import init_defaults

    defaults = init_defaults('myapp', 'daemon')
    defaults['daemon']['user'] = 'myuser'
    defaults['daemon']['group'] = 'mygroup'
    defaults['daemon']['dir'] = '/var/lib/myapp/'
    defaults['daemon']['pid_file'] = '/var/run/myapp/myapp.pid'
    defaults['daemon']['umask'] = 0

    app = CementApp('myapp', config_defaults=defaults)


Application defaults are then overridden by configurations parsed via a
``[demon]`` config section in any of the applications configuration paths.
An example configuration block would look like:

.. code-block:: text

    [daemon]
    user = myuser
    group = mygroup
    dir = /var/lib/myapp/
    pid_file = /var/run/myapp/myapp.pid
    umask = 0


Usage
-----

The following example shows how to add the daemon extension, as well as
trigger daemon functionality before ``app.run()`` is called.

.. code-block:: python

    from time import sleep
    from cement.core.foundation import CementApp

    class MyApp(CementApp):
        class Meta:
            label = 'myapp'
            extensions = ['daemon']

    with MyApp() as app:
        app.daemonize()
        app.run()

        count = 0
        while True:
            count = count + 1
            print('Iteration: %s' % count)
            sleep(10)


An alternative to the above is to put the ``daemonize()`` call within a
framework hook:

.. code-block:: python

    def make_daemon(app):
        app.daemonize()

    def load(app):
        app.hook.register('pre_run', make_daemon)


Finally, some applications may prefer to only daemonize certain sub-commands
rather than the entire parent application.  For example:

.. code-block:: python

    from cement.core.foundation import CementApp
    from cement.core.controller import CementBaseController, expose


    class MyBaseController(CementBaseController):
        class Meta:
            label = 'base'

        @expose(help="run the daemon command.")
        def run_forever(self):
            from time import sleep
            self.app.daemonize()

            count = 0
            while True:
                count = count + 1
                print(count)
                sleep(10)

    class MyApp(CementApp):
        class Meta:
            label = 'myapp'
            base_controller = MyBaseController
            extensions = ['daemon']


    with MyApp() as app:
        app.run()


By default, even after ``app.daemonize()`` is called... the application will
continue to run in the foreground, but will still manage the pid and
user/group switching.  To detach a process and send it to the background you
simply pass the ``--daemon`` option at command line.

.. code-block:: text

    $ python example.py --daemon

    $ ps -x | grep example
    37421 ??         0:00.01 python example2.py --daemon
    37452 ttys000    0:00.00 grep example

"""

import os
import sys
import io
import pwd
import grp
from ..core import exc
from ..utils.misc import minimal_logger

LOG = minimal_logger(__name__)
LOG = minimal_logger(__name__)
CEMENT_DAEMON_ENV = None
CEMENT_DAEMON_APP = None


class Environment(object):

    """
    This class provides a mechanism for altering the running processes
    environment.

    Optional Arguments:

    :keyword stdin: A file to read STDIN from.  Default: ``/dev/null``
    :keyword stdout: A file to write STDOUT to.  Default: ``/dev/null``
    :keyword stderr: A file to write STDERR to.  Default: ``/dev/null``
    :keyword dir: The directory to run the process in.
    :keyword pid_file: The filesystem path to where the PID (Process ID)
        should be written to.  Default: None
    :keyword user: The user name to run the process as.
        Default: ``os.getlogin()``
    :keyword group: The group name to run the process as.
        Default: The primary group of ``os.getlogin()``.
    :keyword umask: The umask to pass to os.umask().  Default: ``0``

    """

    def __init__(self, **kw):
        self.stdin = kw.get('stdin', '/dev/null')
        self.stdout = kw.get('stdout', '/dev/null')
        self.stderr = kw.get('stderr', '/dev/null')
        self.dir = kw.get('dir', os.curdir)
        self.pid_file = kw.get('pid_file', None)
        self.umask = kw.get('umask', 0)
        self.user = kw.get('user', os.getlogin())

        # clean up
        self.dir = os.path.abspath(os.path.expanduser(self.dir))
        if self.pid_file:
            self.pid_file = os.path.abspath(os.path.expanduser(self.pid_file))

        try:
            self.user = pwd.getpwnam(self.user)
        except KeyError as e:
            raise exc.FrameworkError("Daemon user '%s' doesn't exist." %
                                     self.user)

        try:
            self.group = kw.get('group',
                                grp.getgrgid(self.user.pw_gid).gr_name)
            self.group = grp.getgrnam(self.group)
        except KeyError as e:
            raise exc.FrameworkError("Daemon group '%s' doesn't exist." %
                                     self.group)

    def _write_pid_file(self):
        """
        Writes ``os.getpid()`` out to ``self.pid_file``.
        """
        pid = str(os.getpid())
        LOG.debug('writing pid (%s) out to %s' % (pid, self.pid_file))

        # setup pid
        if self.pid_file:
            f = open(self.pid_file, 'w')
            f.write(pid)
            f.close()

            os.chown(self.pid_file, self.user.pw_uid, self.group.gr_gid)

    def switch(self):
        """
        Switch the current process's user/group to ``self.user``, and
        ``self.group``.  Change directory to ``self.dir``, and write the
        current pid out to ``self.pid_file``.
        """
        # set the running uid/gid
        LOG.debug('setting process uid(%s) and gid(%s)' %
                  (self.user.pw_uid, self.group.gr_gid))
        os.setgid(self.group.gr_gid)
        os.setuid(self.user.pw_uid)
        os.environ['HOME'] = self.user.pw_dir
        os.chdir(self.dir)
        if self.pid_file and os.path.exists(self.pid_file):
            raise exc.FrameworkError("Process already running (%s)" %
                                     self.pid_file)
        else:
            self._write_pid_file()

    def daemonize(self):  # pragma: no cover
        """
        Fork the current process into a daemon.

        References:

        UNIX Programming FAQ:
            1.7 How do I get my program to act like a daemon?
            http://www.unixguide.net/unix/programming/1.7.shtml
            http://www.faqs.org/faqs/unix-faq/programmer/faq/

        Advanced Programming in the Unix Environment
            W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.

        """
        LOG.debug('attempting to daemonize the current process')
        # Do first fork.
        try:
            pid = os.fork()
            if pid > 0:
                LOG.debug('successfully detached from first parent')
                os._exit(os.EX_OK)
        except OSError as e:
            sys.stderr.write("Fork #1 failed: (%d) %s\n" %
                             (e.errno, e.strerror))
            sys.exit(1)

        # Decouple from parent environment.
        os.chdir(self.dir)
        os.umask(int(self.umask))
        os.setsid()

        # Do second fork.
        try:
            pid = os.fork()
            if pid > 0:
                LOG.debug('successfully detached from second parent')
                os._exit(os.EX_OK)
        except OSError as e:
            sys.stderr.write("Fork #2 failed: (%d) %s\n" %
                             (e.errno, e.strerror))
            sys.exit(1)

        # Redirect standard file descriptors.
        stdin = open(self.stdin, 'r')
        stdout = open(self.stdout, 'a+')
        stderr = open(self.stderr, 'a+')

        if hasattr(sys.stdin, 'fileno'):
            try:
                os.dup2(stdin.fileno(), sys.stdin.fileno())
            except io.UnsupportedOperation as e:
                # FIXME: ?
                pass
        if hasattr(sys.stdout, 'fileno'):
            try:
                os.dup2(stdout.fileno(), sys.stdout.fileno())
            except io.UnsupportedOperation as e:
                # FIXME: ?
                pass
        if hasattr(sys.stderr, 'fileno'):
            try:
                os.dup2(stderr.fileno(), sys.stderr.fileno())
            except io.UnsupportedOperation as e:
                # FIXME: ?
                pass

        # Update our pid file
        self._write_pid_file()


def daemonize():  # pragma: no cover
    """
    This function switches the running user/group to that configured in
    ``config['daemon']['user']`` and ``config['daemon']['group']``.  The
    default user is ``os.getlogin()`` and the default group is that user's
    primary group.  A pid_file and directory to run in is also passed to the
    environment.

    It is important to note that with the daemon extension enabled, the
    environment will switch user/group/set pid/etc regardless of whether
    the ``--daemon`` option was passed at command line or not.  However, the
    process will only 'daemonize' if the option is passed to do so.  This
    allows the program to run exactly the same in forground or background.

    """
    # We want to honor the runtime user/group/etc even if --daemon is not
    # passed... but only daemonize if it is.
    global CEMENT_DAEMON_ENV
    global CEMENT_DAEMON_APP

    app = CEMENT_DAEMON_APP
    CEMENT_DAEMON_ENV = Environment(
        user=app.config.get('daemon', 'user'),
        group=app.config.get('daemon', 'group'),
        pid_file=app.config.get('daemon', 'pid_file'),
        dir=app.config.get('daemon', 'dir'),
        umask=app.config.get('daemon', 'umask'),
    )

    CEMENT_DAEMON_ENV.switch()

    if '--daemon' in app.argv:
        CEMENT_DAEMON_ENV.daemonize()


def extend_app(app):
    """
    Adds the ``--daemon`` argument to the argument object, and sets the
    default ``[daemon]`` config section options.

    """
    global CEMENT_DAEMON_APP
    CEMENT_DAEMON_APP = app

    app.args.add_argument('--daemon', dest='daemon',
                          action='store_true', help='daemonize the process')

    # Add default config
    user = pwd.getpwnam(os.getlogin())
    group = grp.getgrgid(user.pw_gid)

    defaults = dict()
    defaults['daemon'] = dict()
    defaults['daemon']['user'] = user.pw_name
    defaults['daemon']['group'] = group.gr_name
    defaults['daemon']['pid_file'] = None
    defaults['daemon']['dir'] = '/'
    defaults['daemon']['umask'] = 0
    app.config.merge(defaults, override=False)
    app.extend('daemonize', daemonize)


def cleanup(app):  # pragma: no cover
    """
    After application run time, this hook just attempts to clean up the
    pid_file if one was set, and exists.

    """
    global CEMENT_DAEMON_ENV

    if CEMENT_DAEMON_ENV and CEMENT_DAEMON_ENV.pid_file:
        if os.path.exists(CEMENT_DAEMON_ENV.pid_file):
            LOG.debug('Cleaning up pid_file...')
            pid = open(CEMENT_DAEMON_ENV.pid_file, 'r').read().strip()

            # only remove it if we created it.
            if int(pid) == int(os.getpid()):
                os.remove(CEMENT_DAEMON_ENV.pid_file)


def load(app):
    app.hook.register('post_setup', extend_app)
    app.hook.register('pre_close', cleanup)