Repository: benoitc/gunicorn

Issue:
accesslog set to /dev/null broken in 19.4
Why do I have accesslog file set to `/dev/null`? This seems to be the only sane way to get gunicorn to not print access log to stdout/stderr yet still send it to syslog (with accesslog file not set at all accesslog doesn't go to syslog, tested on 19.3 and 19.4).

This used to work in 19.3, d922df3902c43fc1d761e0e282fbec9b702b273e changed it.

Please let me know if it's expected for `/dev/null` to not work as accesslog file.

Edit:

The actual error:

```
Traceback (most recent call last):
  File "/env/bin/gunicorn", line 11, in <module>
    sys.exit(run())
  File "/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 74, in run
    WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
  File "/env/lib/python2.7/site-packages/gunicorn/app/base.py", line 192, in run
    super(Application, self).run()
  File "/env/lib/python2.7/site-packages/gunicorn/app/base.py", line 72, in run
    Arbiter(self).run()
  File "/env/lib/python2.7/site-packages/gunicorn/arbiter.py", line 61, in __init__
    self.setup(app)
  File "/env/lib/python2.7/site-packages/gunicorn/arbiter.py", line 94, in setup
    self.log = self.cfg.logger_class(app.cfg)
  File "/env/lib/python2.7/site-packages/gunicorn/glogging.py", line 178, in __init__
    self.setup(cfg)
  File "/env/lib/python2.7/site-packages/gunicorn/glogging.py", line 192, in setup
    fmt=logging.Formatter(self.access_fmt))
  File "/env/lib/python2.7/site-packages/gunicorn/glogging.py", line 341, in _set_handler
    os.chown(h.baseFilename, self.cfg.user, self.cfg.group)
OSError: [Errno 1] Operation not permitted: '/dev/null'
```

Edit 2: this is running as non-root user



Discussion:
but what are the permissions of /dev/null ? Normally anyone should have access to it...

``` sh
[~] ls -fla /dev/null                                                                                           11:49:21
crw-rw-rw- 1 root 3, 2 Dec  4 11:49 /dev/null
```

Writing is fine, it's trying to chown it while running as a non-root user that's the issue.

oh right.  Actually I am thinking we should check if the user has enough permission to write on an existing file. I will make such changes tomorrow.

Okay, there are actually two issues described here:
- gunicorn chokes if --access-logfile is set and the user doesn't have permission
- the --access-logfile option is needed to get access logs into syslog

While the title describes the first of these, I think that's not actually an issue. I believe _should_ fail to start if it cannot open the access log.

If we fix the second issue, then there is no need to pass /dev/null to enable the access logging in syslog. I have placed this fix on a branch for review: https://github.com/benoitc/gunicorn/compare/1157-access-syslog?expand=1

@tilgovi just commented your fix. Let's merge it, then check if we still need some action there.

Done in 2f8e750

Thank you @tilgovi and @benoitc, this does indeed fix the "the --access-logfile option is needed to get access logs into syslog" issue.

I'm still somewhat of an opinion that setting a log file to `/dev/null` (or any other file that an application can write to but can't chown) should work regardless but I'll leave it to you to decide whether to close this issue.


Resolving patch:
diff --git a/gunicorn/glogging.py b/gunicorn/glogging.py
--- a/gunicorn/glogging.py
+++ b/gunicorn/glogging.py
@@ -338,8 +338,9 @@ def _set_handler(self, log, output, fmt):
                 util.check_is_writeable(output)
                 h = logging.FileHandler(output)
                 # make sure the user can reopen the file
-                os.chown(h.baseFilename, self.cfg.user, self.cfg.group)
-
+                if not util.is_writable(h.baseFilename, self.cfg.user,
+                        self.cfg.group):
+                    os.chown(h.baseFilename, self.cfg.user, self.cfg.group)
             h.setFormatter(fmt)
             h._gunicorn = True
             log.addHandler(h)
diff --git a/gunicorn/util.py b/gunicorn/util.py
--- a/gunicorn/util.py
+++ b/gunicorn/util.py
@@ -7,12 +7,15 @@
 
 import email.utils
 import fcntl
+import grp
 import io
 import os
 import pkg_resources
+import pwd
 import random
 import resource
 import socket
+import stat
 import sys
 import textwrap
 import time
@@ -159,6 +162,21 @@ def chown(path, uid, gid):
     gid = abs(gid) & 0x7FFFFFFF  # see note above.
     os.chown(path, uid, gid)
 
+def is_writable(path, uid, gid):
+    gid = abs(gid) & 0x7FFFFFFF
+    st = os.stat(path)
+
+    if st.st_uid == uid:
+        return st.st_mode & st.S_IWUSR != 0
+
+    user = pwd.getpwuid(uid)[0]
+    groups = [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]
+    groups.append(gid)
+
+    if st.st_gid in groups:
+        return st.st_mode & stat.S_IWGRP != 0
+
+    return st.st_mode & stat.S_IWOTH != 0
 
 if sys.platform.startswith("win"):
     def _waitfor(func, pathname, waitall=False):


Test patch: