We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Core Agent inheriting and holding open sockets from parent
Reported via Slack:

```

Mike O'Toole 9:12 AM
hey there -- are there any instructions available on installing/starting the core-agent separately from a django app?
cschneid:scout-orange: 9:13 AM
@Mike O'Toole - the binary gets downloaded to /tmp/scout_apm_core/… and has a help command.
Mike O'Toole 9:13 AM
we're running into an issue where the core-agent process seems to hang on to the ports that are uWSGI is binding when it first starts up. Then when we try to restart our uWSGI process, it tries to bind the ports again, and it can't because the core-agent process has still got them
cschneid:scout-orange: 9:14 AM
That is odd, it’s a totally different process, so shouldn’t ever have access to port file descriptors.
Mike O'Toole 9:15 AM
yeah -- that's what i thought too, but looking at lsof this is what im seeing:
core-agen 23301         chuck-uwsgi    8u  IPv4 7817953      0t0  TCP *:3031 (LISTEN)
core-agen 23301         chuck-uwsgi    9u  IPv4 7817954      0t0  TCP *:8080 (LISTEN)
cschneid:scout-orange: 9:15 AM
ok, well that’s not intended… :slightly_smiling_face:
Mike O'Toole 9:15 AM
3031/8080 are the ports that uWSGI is binding
cschneid:scout-orange: 9:16 AM
For now, you’re looking for core-agent start --socket-path <match the default, or the explicit config> 
Mike O'Toole 9:16 AM
sweet, thanks
cschneid:scout-orange: 9:17 AM
the default socket path is:
    def derive_socket_path(self):
        return "{}/{}/core-agent.sock".format(
            self.config.value("core_agent_dir"),
            self.config.value("core_agent_full_name"),
which would be something like: /tmp/scout_apm_core/scout_apm_core-latest-x86_64-apple-darwin/core-agent.sock
I’m really confused by the core agent holding onto those ports. We launch via             subprocess.check_call( ...  which I suppose would maintain file descriptors?
If close_fds is true, all file descriptors except 0, 1 and 2 will be closed before the child process is executed. (POSIX only). The default varies by platform: Always true on POSIX. 
from the docs.
sounds like it should have closed the socket handles?
Mike O'Toole 9:22 AM
hmmm weird
cschneid:scout-orange: 9:22 AM
What version of python are you on? I can try to reproduce
Mike O'Toole 9:24 AM
python 2.7.6
ahh, here's someone having a related issue with uWSGI

https://stackoverflow.com/questions/48376664/uwsgi-subprocess-create-a-new-process-the-socket-close-wait

sounds like that config option is what we need
cschneid:scout-orange: 9:25 AM
ok, the docs I shared were from 3.5, I wonder if that behavior changed
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/core_agent_manager.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import hashlib
5 import json
6 import logging
7 import os
8 import subprocess
9 import tarfile
10 import time
11 
12 import requests
13 
14 from scout_apm.core.context import AgentContext
15 
16 logger = logging.getLogger(__name__)
17 
18 
19 class CoreAgentManager(object):
20     def __init__(self):
21         self.core_agent_bin_path = None
22         self.core_agent_bin_version = None
23         self.core_agent_dir = "{}/{}".format(
24             AgentContext.instance.config.value("core_agent_dir"),
25             AgentContext.instance.config.value("core_agent_full_name"),
26         )
27         self.downloader = CoreAgentDownloader(
28             self.core_agent_dir,
29             AgentContext.instance.config.value("core_agent_full_name"),
30         )
31 
32     def launch(self):
33         if not AgentContext.instance.config.value("core_agent_launch"):
34             logger.debug(
35                 "Not attempting to launch Core Agent "
36                 "due to 'core_agent_launch' setting."
37             )
38             return False
39 
40         if not self.verify():
41             if not AgentContext.instance.config.value("core_agent_download"):
42                 logger.debug(
43                     "Not attempting to download Core Agent due "
44                     "to 'core_agent_download' setting."
45                 )
46                 return False
47 
48             self.download()
49 
50             if not self.verify():
51                 logger.debug("Failed to verify Core Agent. Not launching Core Agent.")
52                 return False
53 
54         return self.run()
55 
56     def download(self):
57         self.downloader.download()
58 
59     def run(self):
60         try:
61             subprocess.check_call(
62                 self.agent_binary()
63                 + self.daemonize_flag()
64                 + self.log_level()
65                 + self.log_file()
66                 + self.config_file()
67                 + self.socket_path()
68             )
69         except Exception:
70             # TODO detect failure of launch properly
71             logger.exception("Error running Core Agent")
72             return False
73         return True
74 
75     def agent_binary(self):
76         return [self.core_agent_bin_path, "start"]
77 
78     def daemonize_flag(self):
79         return ["--daemonize", "true"]
80 
81     def socket_path(self):
82         socket_path = AgentContext.instance.config.value("socket_path")
83         return ["--socket", socket_path]
84 
85     def log_level(self):
86         level = AgentContext.instance.config.value("log_level")
87         return ["--log-level", level]
88 
89     def log_file(self):
90         path = AgentContext.instance.config.value("log_file")
91         if path is not None:
92             return ["--log-file", path]
93         else:
94             return []
95 
96     def config_file(self):
97         path = AgentContext.instance.config.value("config_file")
98         if path is not None:
99             return ["--config-file", path]
100         else:
101             return []
102 
103     def verify(self):
104         manifest = CoreAgentManifest(self.core_agent_dir + "/manifest.json")
105         if not manifest.is_valid():
106             logger.debug(
107                 "Core Agent verification failed: CoreAgentManifest is not valid."
108             )
109             self.core_agent_bin_path = None
110             self.core_agent_bin_version = None
111             return False
112 
113         bin_path = self.core_agent_dir + "/" + manifest.bin_name
114         if sha256_digest(bin_path) == manifest.sha256:
115             self.core_agent_bin_path = bin_path
116             self.core_agent_bin_version = manifest.bin_version
117             return True
118         else:
119             logger.debug("Core Agent verification failed: SHA mismatch.")
120             self.core_agent_bin_path = None
121             self.core_agent_bin_version = None
122             return False
123 
124 
125 class CoreAgentDownloader(object):
126     def __init__(self, download_destination, core_agent_full_name):
127         self.stale_download_secs = 120
128         self.destination = download_destination
129         self.core_agent_full_name = core_agent_full_name
130         self.package_location = self.destination + "/{}.tgz".format(
131             self.core_agent_full_name
132         )
133         self.download_lock_path = self.destination + "/download.lock"
134         self.download_lock_fd = None
135 
136     def download(self):
137         self.create_core_agent_dir()
138         self.obtain_download_lock()
139         if self.download_lock_fd is not None:
140             try:
141                 self.download_package()
142                 self.untar()
143             except OSError:
144                 logger.exception("Exception raised while downloading Core Agent")
145             finally:
146                 self.release_download_lock()
147 
148     def create_core_agent_dir(self):
149         try:
150             os.makedirs(
151                 self.destination, AgentContext.instance.config.core_agent_permissions()
152             )
153         except OSError:
154             pass
155 
156     def obtain_download_lock(self):
157         self.clean_stale_download_lock()
158         try:
159             self.download_lock_fd = os.open(
160                 self.download_lock_path,
161                 os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,
162             )
163         except OSError as e:
164             logger.debug(
165                 "Could not obtain download lock on %s: %r", self.download_lock_path, e
166             )
167             self.download_lock_fd = None
168 
169     def clean_stale_download_lock(self):
170         try:
171             delta = time.time() - os.stat(self.download_lock_path).st_ctime
172             if delta > self.stale_download_secs:
173                 logger.debug("Clearing stale download lock file.")
174                 os.unlink(self.download_lock_path)
175         except OSError:
176             pass
177 
178     def release_download_lock(self):
179         if self.download_lock_fd is not None:
180             os.unlink(self.download_lock_path)
181             os.close(self.download_lock_fd)
182 
183     def download_package(self):
184         logger.debug("Downloading: %s to %s", self.full_url(), self.package_location)
185         req = requests.get(self.full_url(), stream=True)
186         with open(self.package_location, "wb") as f:
187             for chunk in req.iter_content(1024 * 1000):
188                 f.write(chunk)
189 
190     def untar(self):
191         t = tarfile.open(self.package_location, "r")
192         t.extractall(self.destination)
193 
194     def full_url(self):
195         return "{root_url}/{core_agent_full_name}.tgz".format(
196             root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name
197         )
198 
199     def root_url(self):
200         return AgentContext.instance.config.value("download_url")
201 
202 
203 class CoreAgentManifest(object):
204     def __init__(self, path):
205         self.manifest_path = path
206         self.bin_name = None
207         self.bin_version = None
208         self.sha256 = None
209         self.valid = False
210         try:
211             self.parse()
212         except (ValueError, TypeError, OSError, IOError) as e:
213             logger.debug("Error parsing Core Agent Manifest: %r", e)
214 
215     def parse(self):
216         logger.debug("Parsing Core Agent manifest path: %s", self.manifest_path)
217         with open(self.manifest_path) as manifest_file:
218             self.raw = manifest_file.read()
219             self.json = json.loads(self.raw)
220             self.version = self.json["version"]
221             self.bin_version = self.json["core_agent_version"]
222             self.bin_name = self.json["core_agent_binary"]
223             self.sha256 = self.json["core_agent_binary_sha256"]
224             self.valid = True
225             logger.debug("Core Agent manifest json: %s", self.json)
226 
227     def is_valid(self):
228         return self.valid
229 
230 
231 def sha256_digest(filename, block_size=65536):
232     try:
233         sha256 = hashlib.sha256()
234         with open(filename, "rb") as f:
235             for block in iter(lambda: f.read(block_size), b""):
236                 sha256.update(block)
237         return sha256.hexdigest()
238     except OSError as e:
239         logger.debug("Error on digest: %r", e)
240         return None
241
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/core/core_agent_manager.py b/src/scout_apm/core/core_agent_manager.py
--- a/src/scout_apm/core/core_agent_manager.py
+++ b/src/scout_apm/core/core_agent_manager.py
@@ -59,12 +59,15 @@
     def run(self):
         try:
             subprocess.check_call(
-                self.agent_binary()
-                + self.daemonize_flag()
-                + self.log_level()
-                + self.log_file()
-                + self.config_file()
-                + self.socket_path()
+                (
+                    self.agent_binary()
+                    + self.daemonize_flag()
+                    + self.log_level()
+                    + self.log_file()
+                    + self.config_file()
+                    + self.socket_path()
+                ),
+                close_fds=True,
             )
         except Exception:
             # TODO detect failure of launch properly

{"golden_diff": "diff --git a/src/scout_apm/core/core_agent_manager.py b/src/scout_apm/core/core_agent_manager.py\n--- a/src/scout_apm/core/core_agent_manager.py\n+++ b/src/scout_apm/core/core_agent_manager.py\n@@ -59,12 +59,15 @@\n     def run(self):\n         try:\n             subprocess.check_call(\n-                self.agent_binary()\n-                + self.daemonize_flag()\n-                + self.log_level()\n-                + self.log_file()\n-                + self.config_file()\n-                + self.socket_path()\n+                (\n+                    self.agent_binary()\n+                    + self.daemonize_flag()\n+                    + self.log_level()\n+                    + self.log_file()\n+                    + self.config_file()\n+                    + self.socket_path()\n+                ),\n+                close_fds=True,\n             )\n         except Exception:\n             # TODO detect failure of launch properly\n", "issue": "Core Agent inheriting and holding open sockets from parent\nReported via Slack:\r\n\r\n```\r\n\r\nMike O'Toole 9:12 AM\r\nhey there -- are there any instructions available on installing/starting the core-agent separately from a django app?\r\ncschneid:scout-orange: 9:13 AM\r\n@Mike O'Toole - the binary gets downloaded to /tmp/scout_apm_core/\u2026 and has a help command.\r\nMike O'Toole 9:13 AM\r\nwe're running into an issue where the core-agent process seems to hang on to the ports that are uWSGI is binding when it first starts up. Then when we try to restart our uWSGI process, it tries to bind the ports again, and it can't because the core-agent process has still got them\r\ncschneid:scout-orange: 9:14 AM\r\nThat is odd, it\u2019s a totally different process, so shouldn\u2019t ever have access to port file descriptors.\r\nMike O'Toole 9:15 AM\r\nyeah -- that's what i thought too, but looking at lsof this is what im seeing:\r\ncore-agen 23301         chuck-uwsgi    8u  IPv4 7817953      0t0  TCP *:3031 (LISTEN)\r\ncore-agen 23301         chuck-uwsgi    9u  IPv4 7817954      0t0  TCP *:8080 (LISTEN)\r\ncschneid:scout-orange: 9:15 AM\r\nok, well that\u2019s not intended\u2026 :slightly_smiling_face:\r\nMike O'Toole 9:15 AM\r\n3031/8080 are the ports that uWSGI is binding\r\ncschneid:scout-orange: 9:16 AM\r\nFor now, you\u2019re looking for core-agent start --socket-path <match the default, or the explicit config> \r\nMike O'Toole 9:16 AM\r\nsweet, thanks\r\ncschneid:scout-orange: 9:17 AM\r\nthe default socket path is:\r\n    def derive_socket_path(self):\r\n        return \"{}/{}/core-agent.sock\".format(\r\n            self.config.value(\"core_agent_dir\"),\r\n            self.config.value(\"core_agent_full_name\"),\r\nwhich would be something like: /tmp/scout_apm_core/scout_apm_core-latest-x86_64-apple-darwin/core-agent.sock\r\nI\u2019m really confused by the core agent holding onto those ports. We launch via             subprocess.check_call( ...  which I suppose would maintain file descriptors?\r\nIf close_fds is true, all file descriptors except 0, 1 and 2 will be closed before the child process is executed. (POSIX only). The default varies by platform: Always true on POSIX. \r\nfrom the docs.\r\nsounds like it should have closed the socket handles?\r\nMike O'Toole 9:22 AM\r\nhmmm weird\r\ncschneid:scout-orange: 9:22 AM\r\nWhat version of python are you on? I can try to reproduce\r\nMike O'Toole 9:24 AM\r\npython 2.7.6\r\nahh, here's someone having a related issue with uWSGI\r\n\r\nhttps://stackoverflow.com/questions/48376664/uwsgi-subprocess-create-a-new-process-the-socket-close-wait\r\n\r\nsounds like that config option is what we need\r\ncschneid:scout-orange: 9:25 AM\r\nok, the docs I shared were from 3.5, I wonder if that behavior changed\r\n```\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport hashlib\nimport json\nimport logging\nimport os\nimport subprocess\nimport tarfile\nimport time\n\nimport requests\n\nfrom scout_apm.core.context import AgentContext\n\nlogger = logging.getLogger(__name__)\n\n\nclass CoreAgentManager(object):\n    def __init__(self):\n        self.core_agent_bin_path = None\n        self.core_agent_bin_version = None\n        self.core_agent_dir = \"{}/{}\".format(\n            AgentContext.instance.config.value(\"core_agent_dir\"),\n            AgentContext.instance.config.value(\"core_agent_full_name\"),\n        )\n        self.downloader = CoreAgentDownloader(\n            self.core_agent_dir,\n            AgentContext.instance.config.value(\"core_agent_full_name\"),\n        )\n\n    def launch(self):\n        if not AgentContext.instance.config.value(\"core_agent_launch\"):\n            logger.debug(\n                \"Not attempting to launch Core Agent \"\n                \"due to 'core_agent_launch' setting.\"\n            )\n            return False\n\n        if not self.verify():\n            if not AgentContext.instance.config.value(\"core_agent_download\"):\n                logger.debug(\n                    \"Not attempting to download Core Agent due \"\n                    \"to 'core_agent_download' setting.\"\n                )\n                return False\n\n            self.download()\n\n            if not self.verify():\n                logger.debug(\"Failed to verify Core Agent. Not launching Core Agent.\")\n                return False\n\n        return self.run()\n\n    def download(self):\n        self.downloader.download()\n\n    def run(self):\n        try:\n            subprocess.check_call(\n                self.agent_binary()\n                + self.daemonize_flag()\n                + self.log_level()\n                + self.log_file()\n                + self.config_file()\n                + self.socket_path()\n            )\n        except Exception:\n            # TODO detect failure of launch properly\n            logger.exception(\"Error running Core Agent\")\n            return False\n        return True\n\n    def agent_binary(self):\n        return [self.core_agent_bin_path, \"start\"]\n\n    def daemonize_flag(self):\n        return [\"--daemonize\", \"true\"]\n\n    def socket_path(self):\n        socket_path = AgentContext.instance.config.value(\"socket_path\")\n        return [\"--socket\", socket_path]\n\n    def log_level(self):\n        level = AgentContext.instance.config.value(\"log_level\")\n        return [\"--log-level\", level]\n\n    def log_file(self):\n        path = AgentContext.instance.config.value(\"log_file\")\n        if path is not None:\n            return [\"--log-file\", path]\n        else:\n            return []\n\n    def config_file(self):\n        path = AgentContext.instance.config.value(\"config_file\")\n        if path is not None:\n            return [\"--config-file\", path]\n        else:\n            return []\n\n    def verify(self):\n        manifest = CoreAgentManifest(self.core_agent_dir + \"/manifest.json\")\n        if not manifest.is_valid():\n            logger.debug(\n                \"Core Agent verification failed: CoreAgentManifest is not valid.\"\n            )\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n        bin_path = self.core_agent_dir + \"/\" + manifest.bin_name\n        if sha256_digest(bin_path) == manifest.sha256:\n            self.core_agent_bin_path = bin_path\n            self.core_agent_bin_version = manifest.bin_version\n            return True\n        else:\n            logger.debug(\"Core Agent verification failed: SHA mismatch.\")\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n\nclass CoreAgentDownloader(object):\n    def __init__(self, download_destination, core_agent_full_name):\n        self.stale_download_secs = 120\n        self.destination = download_destination\n        self.core_agent_full_name = core_agent_full_name\n        self.package_location = self.destination + \"/{}.tgz\".format(\n            self.core_agent_full_name\n        )\n        self.download_lock_path = self.destination + \"/download.lock\"\n        self.download_lock_fd = None\n\n    def download(self):\n        self.create_core_agent_dir()\n        self.obtain_download_lock()\n        if self.download_lock_fd is not None:\n            try:\n                self.download_package()\n                self.untar()\n            except OSError:\n                logger.exception(\"Exception raised while downloading Core Agent\")\n            finally:\n                self.release_download_lock()\n\n    def create_core_agent_dir(self):\n        try:\n            os.makedirs(\n                self.destination, AgentContext.instance.config.core_agent_permissions()\n            )\n        except OSError:\n            pass\n\n    def obtain_download_lock(self):\n        self.clean_stale_download_lock()\n        try:\n            self.download_lock_fd = os.open(\n                self.download_lock_path,\n                os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,\n            )\n        except OSError as e:\n            logger.debug(\n                \"Could not obtain download lock on %s: %r\", self.download_lock_path, e\n            )\n            self.download_lock_fd = None\n\n    def clean_stale_download_lock(self):\n        try:\n            delta = time.time() - os.stat(self.download_lock_path).st_ctime\n            if delta > self.stale_download_secs:\n                logger.debug(\"Clearing stale download lock file.\")\n                os.unlink(self.download_lock_path)\n        except OSError:\n            pass\n\n    def release_download_lock(self):\n        if self.download_lock_fd is not None:\n            os.unlink(self.download_lock_path)\n            os.close(self.download_lock_fd)\n\n    def download_package(self):\n        logger.debug(\"Downloading: %s to %s\", self.full_url(), self.package_location)\n        req = requests.get(self.full_url(), stream=True)\n        with open(self.package_location, \"wb\") as f:\n            for chunk in req.iter_content(1024 * 1000):\n                f.write(chunk)\n\n    def untar(self):\n        t = tarfile.open(self.package_location, \"r\")\n        t.extractall(self.destination)\n\n    def full_url(self):\n        return \"{root_url}/{core_agent_full_name}.tgz\".format(\n            root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name\n        )\n\n    def root_url(self):\n        return AgentContext.instance.config.value(\"download_url\")\n\n\nclass CoreAgentManifest(object):\n    def __init__(self, path):\n        self.manifest_path = path\n        self.bin_name = None\n        self.bin_version = None\n        self.sha256 = None\n        self.valid = False\n        try:\n            self.parse()\n        except (ValueError, TypeError, OSError, IOError) as e:\n            logger.debug(\"Error parsing Core Agent Manifest: %r\", e)\n\n    def parse(self):\n        logger.debug(\"Parsing Core Agent manifest path: %s\", self.manifest_path)\n        with open(self.manifest_path) as manifest_file:\n            self.raw = manifest_file.read()\n            self.json = json.loads(self.raw)\n            self.version = self.json[\"version\"]\n            self.bin_version = self.json[\"core_agent_version\"]\n            self.bin_name = self.json[\"core_agent_binary\"]\n            self.sha256 = self.json[\"core_agent_binary_sha256\"]\n            self.valid = True\n            logger.debug(\"Core Agent manifest json: %s\", self.json)\n\n    def is_valid(self):\n        return self.valid\n\n\ndef sha256_digest(filename, block_size=65536):\n    try:\n        sha256 = hashlib.sha256()\n        with open(filename, \"rb\") as f:\n            for block in iter(lambda: f.read(block_size), b\"\"):\n                sha256.update(block)\n        return sha256.hexdigest()\n    except OSError as e:\n        logger.debug(\"Error on digest: %r\", e)\n        return None\n", "path": "src/scout_apm/core/core_agent_manager.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport hashlib\nimport json\nimport logging\nimport os\nimport subprocess\nimport tarfile\nimport time\n\nimport requests\n\nfrom scout_apm.core.context import AgentContext\n\nlogger = logging.getLogger(__name__)\n\n\nclass CoreAgentManager(object):\n    def __init__(self):\n        self.core_agent_bin_path = None\n        self.core_agent_bin_version = None\n        self.core_agent_dir = \"{}/{}\".format(\n            AgentContext.instance.config.value(\"core_agent_dir\"),\n            AgentContext.instance.config.value(\"core_agent_full_name\"),\n        )\n        self.downloader = CoreAgentDownloader(\n            self.core_agent_dir,\n            AgentContext.instance.config.value(\"core_agent_full_name\"),\n        )\n\n    def launch(self):\n        if not AgentContext.instance.config.value(\"core_agent_launch\"):\n            logger.debug(\n                \"Not attempting to launch Core Agent \"\n                \"due to 'core_agent_launch' setting.\"\n            )\n            return False\n\n        if not self.verify():\n            if not AgentContext.instance.config.value(\"core_agent_download\"):\n                logger.debug(\n                    \"Not attempting to download Core Agent due \"\n                    \"to 'core_agent_download' setting.\"\n                )\n                return False\n\n            self.download()\n\n            if not self.verify():\n                logger.debug(\"Failed to verify Core Agent. Not launching Core Agent.\")\n                return False\n\n        return self.run()\n\n    def download(self):\n        self.downloader.download()\n\n    def run(self):\n        try:\n            subprocess.check_call(\n                (\n                    self.agent_binary()\n                    + self.daemonize_flag()\n                    + self.log_level()\n                    + self.log_file()\n                    + self.config_file()\n                    + self.socket_path()\n                ),\n                close_fds=True,\n            )\n        except Exception:\n            # TODO detect failure of launch properly\n            logger.exception(\"Error running Core Agent\")\n            return False\n        return True\n\n    def agent_binary(self):\n        return [self.core_agent_bin_path, \"start\"]\n\n    def daemonize_flag(self):\n        return [\"--daemonize\", \"true\"]\n\n    def socket_path(self):\n        socket_path = AgentContext.instance.config.value(\"socket_path\")\n        return [\"--socket\", socket_path]\n\n    def log_level(self):\n        level = AgentContext.instance.config.value(\"log_level\")\n        return [\"--log-level\", level]\n\n    def log_file(self):\n        path = AgentContext.instance.config.value(\"log_file\")\n        if path is not None:\n            return [\"--log-file\", path]\n        else:\n            return []\n\n    def config_file(self):\n        path = AgentContext.instance.config.value(\"config_file\")\n        if path is not None:\n            return [\"--config-file\", path]\n        else:\n            return []\n\n    def verify(self):\n        manifest = CoreAgentManifest(self.core_agent_dir + \"/manifest.json\")\n        if not manifest.is_valid():\n            logger.debug(\n                \"Core Agent verification failed: CoreAgentManifest is not valid.\"\n            )\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n        bin_path = self.core_agent_dir + \"/\" + manifest.bin_name\n        if sha256_digest(bin_path) == manifest.sha256:\n            self.core_agent_bin_path = bin_path\n            self.core_agent_bin_version = manifest.bin_version\n            return True\n        else:\n            logger.debug(\"Core Agent verification failed: SHA mismatch.\")\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n\nclass CoreAgentDownloader(object):\n    def __init__(self, download_destination, core_agent_full_name):\n        self.stale_download_secs = 120\n        self.destination = download_destination\n        self.core_agent_full_name = core_agent_full_name\n        self.package_location = self.destination + \"/{}.tgz\".format(\n            self.core_agent_full_name\n        )\n        self.download_lock_path = self.destination + \"/download.lock\"\n        self.download_lock_fd = None\n\n    def download(self):\n        self.create_core_agent_dir()\n        self.obtain_download_lock()\n        if self.download_lock_fd is not None:\n            try:\n                self.download_package()\n                self.untar()\n            except OSError:\n                logger.exception(\"Exception raised while downloading Core Agent\")\n            finally:\n                self.release_download_lock()\n\n    def create_core_agent_dir(self):\n        try:\n            os.makedirs(\n                self.destination, AgentContext.instance.config.core_agent_permissions()\n            )\n        except OSError:\n            pass\n\n    def obtain_download_lock(self):\n        self.clean_stale_download_lock()\n        try:\n            self.download_lock_fd = os.open(\n                self.download_lock_path,\n                os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,\n            )\n        except OSError as e:\n            logger.debug(\n                \"Could not obtain download lock on %s: %r\", self.download_lock_path, e\n            )\n            self.download_lock_fd = None\n\n    def clean_stale_download_lock(self):\n        try:\n            delta = time.time() - os.stat(self.download_lock_path).st_ctime\n            if delta > self.stale_download_secs:\n                logger.debug(\"Clearing stale download lock file.\")\n                os.unlink(self.download_lock_path)\n        except OSError:\n            pass\n\n    def release_download_lock(self):\n        if self.download_lock_fd is not None:\n            os.unlink(self.download_lock_path)\n            os.close(self.download_lock_fd)\n\n    def download_package(self):\n        logger.debug(\"Downloading: %s to %s\", self.full_url(), self.package_location)\n        req = requests.get(self.full_url(), stream=True)\n        with open(self.package_location, \"wb\") as f:\n            for chunk in req.iter_content(1024 * 1000):\n                f.write(chunk)\n\n    def untar(self):\n        t = tarfile.open(self.package_location, \"r\")\n        t.extractall(self.destination)\n\n    def full_url(self):\n        return \"{root_url}/{core_agent_full_name}.tgz\".format(\n            root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name\n        )\n\n    def root_url(self):\n        return AgentContext.instance.config.value(\"download_url\")\n\n\nclass CoreAgentManifest(object):\n    def __init__(self, path):\n        self.manifest_path = path\n        self.bin_name = None\n        self.bin_version = None\n        self.sha256 = None\n        self.valid = False\n        try:\n            self.parse()\n        except (ValueError, TypeError, OSError, IOError) as e:\n            logger.debug(\"Error parsing Core Agent Manifest: %r\", e)\n\n    def parse(self):\n        logger.debug(\"Parsing Core Agent manifest path: %s\", self.manifest_path)\n        with open(self.manifest_path) as manifest_file:\n            self.raw = manifest_file.read()\n            self.json = json.loads(self.raw)\n            self.version = self.json[\"version\"]\n            self.bin_version = self.json[\"core_agent_version\"]\n            self.bin_name = self.json[\"core_agent_binary\"]\n            self.sha256 = self.json[\"core_agent_binary_sha256\"]\n            self.valid = True\n            logger.debug(\"Core Agent manifest json: %s\", self.json)\n\n    def is_valid(self):\n        return self.valid\n\n\ndef sha256_digest(filename, block_size=65536):\n    try:\n        sha256 = hashlib.sha256()\n        with open(filename, \"rb\") as f:\n            for block in iter(lambda: f.read(block_size), b\"\"):\n                sha256.update(block)\n        return sha256.hexdigest()\n    except OSError as e:\n        logger.debug(\"Error on digest: %r\", e)\n        return None\n", "path": "src/scout_apm/core/core_agent_manager.py"}]}